Skip to content

Commit

Permalink
Merge pull request #244 from syntasso/242/auth-for-promise-release
Browse files Browse the repository at this point in the history
Feat: Accept authorization header for promise release URL fetch
  • Loading branch information
abangser authored Oct 1, 2024
2 parents 0314320 + ee3be3c commit eab13f1
Show file tree
Hide file tree
Showing 19 changed files with 266 additions and 86 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust

generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
go generate ./...

##@ Environment

Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ package v1alpha1
//
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . PromiseFetcher
type PromiseFetcher interface {
FromURL(string) (*Promise, error)
FromURL(string, string) (*Promise, error)
}
30 changes: 30 additions & 0 deletions api/v1alpha1/promiserelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ limitations under the License.
package v1alpha1

import (
"context"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const TypeHTTP = "http"
Expand All @@ -33,6 +37,10 @@ type SourceRef struct {
// +kubebuilder:validation:Enum:={http}
Type string `json:"type"`
URL string `json:"url,omitempty"`
// Reference a secret with credentials to access the source.
// For more details on the secret format, see the documentation:
// https://docs.kratix.io/main/reference/promises/releases#promise-release
SecretRef *corev1.SecretReference `json:"secretRef,omitempty"`
}

// PromiseReleaseStatus defines the observed state of PromiseRelease
Expand Down Expand Up @@ -68,3 +76,25 @@ type PromiseReleaseList struct {
func init() {
SchemeBuilder.Register(&PromiseRelease{}, &PromiseReleaseList{})
}

func (pr *PromiseRelease) FetchSecretFromReference() (map[string][]byte, error) {
if pr.Spec.SourceRef.SecretRef == nil {
return nil, nil
}

fetchSecret := &corev1.Secret{}
ns := pr.Namespace
if pr.Spec.SourceRef.SecretRef.Namespace != "" {
ns = pr.Spec.SourceRef.SecretRef.Namespace
}
namespacedName := client.ObjectKey{
Namespace: ns,
Name: pr.Spec.SourceRef.SecretRef.Name,
}

err := k8sClient.Get(context.TODO(), namespacedName, fetchSecret)
if err != nil {
return nil, err
}
return fetchSecret.Data, nil
}
16 changes: 14 additions & 2 deletions api/v1alpha1/promiserelease_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
Expand All @@ -36,7 +37,8 @@ var (
promisereleaselog = logf.Log.WithName("promiserelease-resource")
)

func (r *PromiseRelease) SetupWebhookWithManager(mgr ctrl.Manager, pf PromiseFetcher) error {
func (r *PromiseRelease) SetupWebhookWithManager(mgr ctrl.Manager, c client.Client, pf PromiseFetcher) error {
k8sClient = c
promiseFetcher = pf
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Expand All @@ -52,7 +54,17 @@ func (r *PromiseRelease) ValidateCreate() (admission.Warnings, error) {
return nil, err
}

promise, err := promiseFetcher.FromURL(r.Spec.SourceRef.URL)
secretRefData, err := r.FetchSecretFromReference()
if err != nil {
return nil, fmt.Errorf("failed to fetch data from secretRef: %w", err)
}

authHeader, exists := secretRefData["authorizationHeader"]
if !exists {
authHeader = []byte("")
}

promise, err := promiseFetcher.FromURL(r.Spec.SourceRef.URL, string(authHeader))
if err != nil {
return nil, fmt.Errorf("failed to fetch promise: %w", err)
}
Expand Down
18 changes: 10 additions & 8 deletions api/v1alpha1/v1alpha1fakes/fake_promise_fetcher.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions config/crd/bases/platform.kratix.io_promisereleases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ spec:
default:
type: http
properties:
secretRef:
description: |-
SecretReference represents a Secret Reference. It has enough information to retrieve secret
in any namespace
properties:
name:
description: name is unique within a namespace to reference
a secret resource.
type: string
namespace:
description: namespace defines the space within which the
secret name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
type:
enum:
- http
Expand Down
2 changes: 1 addition & 1 deletion controllers/destination_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type DestinationReconciler struct {

//+kubebuilder:rbac:groups=platform.kratix.io,resources=destinations,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=platform.kratix.io,resources=bucketstatestores;gitstatestores,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch // Also used by promiseRelease
//+kubebuilder:rbac:groups=platform.kratix.io,resources=destinations/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=platform.kratix.io,resources=destinations/finalizers,verbs=update

Expand Down
12 changes: 11 additions & 1 deletion controllers/promiserelease_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,19 @@ func (r *PromiseReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Reque

var promise *v1alpha1.Promise

secretRefData, err := promiseRelease.FetchSecretFromReference()
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to fetch data from secretRef: %w", err)
}

authHeader := ""
if secretRefData != nil {
authHeader = string(secretRefData["authorizationHeader"])
}

switch sourceRefType := promiseRelease.Spec.SourceRef.Type; sourceRefType {
case v1alpha1.TypeHTTP:
promise, err = r.PromiseFetcher.FromURL(promiseRelease.Spec.SourceRef.URL)
promise, err = r.PromiseFetcher.FromURL(promiseRelease.Spec.SourceRef.URL, authHeader)
if err != nil {
r.updateStatusAndConditions(opts, promiseRelease, statusErrorInstalling, "Failed to fetch Promise from URL", "FailedToFetchPromise")
return ctrl.Result{}, fmt.Errorf("failed to fetch promise from url: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions hack/kratix-promise-release-test-hoster-image/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=${TARGETPLATFORM} golang:1.21 as builder
FROM golang:1.21 AS builder
ARG TARGETARCH
ARG TARGETOS

Expand All @@ -12,7 +12,7 @@ RUN go mod download
# Build work-creator binary
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GO111MODULE=on go build -a main.go

FROM --platform=${TARGETPLATFORM} alpine
FROM alpine
# Use rancher/kubectl to get ./kubeconfig
WORKDIR /

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#!/usr/bin/env sh

export IMG="syntassodev/kratix-promise-release-test-hoster:v0.0.2"
export IMG="syntassodev/kratix-promise-release-test-hoster:v0.0.3"

if ! docker buildx ls | grep -q "kratix-image-builder"; then \
docker buildx create --name kratix-image-builder; \
fi;

docker buildx build --builder kratix-image-builder --push --platform linux/arm64,linux/amd64 -t ${IMG} .

22 changes: 11 additions & 11 deletions hack/kratix-promise-release-test-hoster-image/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ spec:
kratix-promise-release: pod
spec:
containers:
- image: syntassodev/kratix-promise-release-test-hoster:v0.0.2
name: test
resources: {}
env:
- name: PROMISE_ENCODED
value: "REPLACEME"
ports:
- containerPort: 8080
- image: syntassodev/kratix-promise-release-test-hoster:v0.0.3
name: test
resources: {}
env:
- name: PROMISE_ENCODED
value: "REPLACEME"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
Expand All @@ -33,9 +33,9 @@ metadata:
namespace: kratix-platform-system
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
- port: 8080
protocol: TCP
targetPort: 8080
selector:
kratix-promise-release: pod
sessionAffinity: None
Expand Down
32 changes: 28 additions & 4 deletions hack/kratix-promise-release-test-hoster-image/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,39 @@ import (
"log"
"os"

gohttp "net/http"
"net/http"

"encoding/base64"

"github.com/gorilla/mux"
)

func SecureHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
fmt.Println("fetching promise: " + name)

authHeader := r.Header.Get("Authorization")
fmt.Println("Checking Authorization header:" + authHeader)
if authHeader == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if authHeader != "Bearer your-secret-token" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

promiseContent, err := base64.StdEncoding.DecodeString(os.Getenv(name))
if err != nil {
panic(err)
}
w.Write(promiseContent)
}

func main() {
router := mux.NewRouter()
router.HandleFunc("/promise/{name}", func(w gohttp.ResponseWriter, r *gohttp.Request) {
router.HandleFunc("/promise/{name}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
fmt.Println("fetching promise: " + name)
Expand All @@ -24,13 +47,14 @@ func main() {
}
w.Write(promiseContent)
}).Methods("GET")
router.HandleFunc("/secure/promise/{name}", SecureHandler).Methods("GET")

srv := &gohttp.Server{
srv := &http.Server{
Addr: ":8080",
Handler: router,
}

if err := srv.ListenAndServe(); err != nil && err != gohttp.ErrServerClosed {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}
17 changes: 15 additions & 2 deletions lib/fetchers/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fetchers
import (
"fmt"
"net/http"
"time"

"github.com/syntasso/kratix/api/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -13,8 +14,20 @@ import (
type URLFetcher struct {
}

func (u *URLFetcher) FromURL(urlString string) (*v1alpha1.Promise, error) {
resp, err := http.Get(urlString)
func (u *URLFetcher) FromURL(urlString, authHeader string) (*v1alpha1.Promise, error) {
req, err := http.NewRequest("GET", urlString, nil)
if err != nil {
return nil, err
}

if authHeader != "" {
req.Header.Add("Authorization", authHeader)
}

client := &http.Client{
Timeout: time.Second * 10,
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get url: %w", err)
}
Expand Down
Loading

0 comments on commit eab13f1

Please sign in to comment.