Skip to content

Commit

Permalink
OIDC targets
Browse files Browse the repository at this point in the history
  • Loading branch information
robertgraeff committed Sep 18, 2024
1 parent c2bddf5 commit 562969f
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 16 deletions.
19 changes: 16 additions & 3 deletions apis/core/v1alpha1/targettypes/kubernetes_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const KubernetesClusterTargetType v1alpha1.TargetType = core.GroupName + "/kuber
type KubernetesClusterTargetConfig struct {
// Kubeconfig defines kubeconfig as string.
Kubeconfig ValueRef `json:"kubeconfig"`

OIDCConfig *OIDCConfig `json:"oidcConfig,omitempty"`
}

// DefaultKubeconfigKey is the default that is used to hold a kubeconfig.
Expand All @@ -35,7 +37,8 @@ type ValueRef struct {

// kubeconfigJSON is a helper struct for decoding.
type kubeconfigJSON struct {
Kubeconfig *ValueRef `json:"kubeconfig"`
Kubeconfig *ValueRef `json:"kubeconfig"`
OIDCConfig *OIDCConfig `json:"oidcConfig,omitempty"`
}

// valueRefJSON is a helper struct to decode json into a secret ref object.
Expand Down Expand Up @@ -77,9 +80,12 @@ func (v *ValueRef) UnmarshalJSON(data []byte) error {
func (kc *KubernetesClusterTargetConfig) UnmarshalJSON(data []byte) error {
kj := &kubeconfigJSON{}
err := json.Unmarshal(data, kj)
if err == nil && kj.Kubeconfig != nil {
if err == nil && (kj.Kubeconfig != nil || kj.OIDCConfig != nil) {
// parsing was successful
kc.Kubeconfig = *kj.Kubeconfig
if kj.Kubeconfig != nil {
kc.Kubeconfig = *kj.Kubeconfig
}
kc.OIDCConfig = kj.OIDCConfig
return nil
}
return kc.Kubeconfig.UnmarshalJSON(data)
Expand All @@ -92,3 +98,10 @@ func (v ValueRef) OpenAPISchemaType() []string {
}
}
func (v ValueRef) OpenAPISchemaFormat() string { return "" }

type OIDCConfig struct {
Server string `json:"server,omitempty"`
CACertificate []byte `json:"caCertificate,omitempty"`
ServiceAccount string `json:"serviceAccount,omitempty"`
Audience []string `json:"audience,omitempty"`
}
107 changes: 107 additions & 0 deletions apis/core/v1alpha1/targettypes/kubernetes_cluster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package targettypes_test

import (
"encoding/json"
"github.com/gardener/landscaper/apis/core/v1alpha1"
"github.com/gardener/landscaper/apis/core/v1alpha1/targettypes"
"k8s.io/utils/ptr"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Types Testing")
}

var _ = Describe("Kubernetes Cluster Target Types", func() {

It("should marshal a kubeconfig", func() {
targetConfig := &targettypes.KubernetesClusterTargetConfig{
Kubeconfig: targettypes.ValueRef{
StrVal: ptr.To("test-kubeconfig"),
},
}
targetConfigJSON, err := json.Marshal(targetConfig)
Expect(err).NotTo(HaveOccurred())
Expect(targetConfigJSON).To(MatchJSON(`{"kubeconfig":"test-kubeconfig"}`))
})

It("should unmarshal a kubeconfig", func() {
configJSON := []byte(`{"kubeconfig":"test-kubeconfig"}`)
config := &targettypes.KubernetesClusterTargetConfig{}
Expect(json.Unmarshal(configJSON, config)).To(Succeed())
Expect(config).To(Equal(&targettypes.KubernetesClusterTargetConfig{
Kubeconfig: targettypes.ValueRef{
StrVal: ptr.To("test-kubeconfig"),
},
}))
})

It("should marshal an old secretRef", func() {
targetConfig := &targettypes.KubernetesClusterTargetConfig{
Kubeconfig: targettypes.ValueRef{
SecretRef: &v1alpha1.SecretReference{
ObjectReference: v1alpha1.ObjectReference{
Name: "test-secret-name",
Namespace: "test-namespace",
},
Key: "test-key",
},
},
}
targetConfigBytes, err := json.Marshal(targetConfig)
Expect(err).NotTo(HaveOccurred())
Expect(targetConfigBytes).To(MatchJSON(`{"kubeconfig":{"secretRef":{"name":"test-secret-name","namespace":"test-namespace","key":"test-key"}}}`))
})

It("should unmarshal an old secretRef", func() {
configJSON := []byte(`{"kubeconfig":{"secretRef":{"name":"test-secret-name","namespace":"test-namespace","key":"test-key"}}}`)
config := &targettypes.KubernetesClusterTargetConfig{}
Expect(json.Unmarshal(configJSON, config)).To(Succeed())
Expect(config).To(Equal(&targettypes.KubernetesClusterTargetConfig{
Kubeconfig: targettypes.ValueRef{
SecretRef: &v1alpha1.SecretReference{
ObjectReference: v1alpha1.ObjectReference{
Name: "test-secret-name",
Namespace: "test-namespace",
},
Key: "test-key",
},
},
}))
})

It("should marshal an oidc config", func() {
targetConfig := &targettypes.KubernetesClusterTargetConfig{
OIDCConfig: &targettypes.OIDCConfig{
Server: "test-server",
CACertificate: []byte("test-cert"),
ServiceAccount: "test-account",
Audience: []string{"test-audience"},
},
}
targetConfigJSON, err := json.Marshal(targetConfig)
Expect(err).NotTo(HaveOccurred())
Expect(targetConfigJSON).To(MatchJSON(`{"kubeconfig":{},"oidcConfig":{"server":"test-server","caCertificate":"dGVzdC1jZXJ0","serviceAccount":"test-account","audience":["test-audience"]}}`))
})

It("should unmarshal an oidc config", func() {
configJSON := []byte(`{"kubeconfig":{},"oidcConfig":{"server":"test-server","caCertificate":"dGVzdC1jZXJ0","serviceAccount":"test-account","audience":["test-audience"]}}`)
config := &targettypes.KubernetesClusterTargetConfig{}
Expect(json.Unmarshal(configJSON, config)).To(Succeed())
Expect(config).To(Equal(&targettypes.KubernetesClusterTargetConfig{
Kubeconfig: targettypes.ValueRef{
StrVal: ptr.To("{}"),
},
OIDCConfig: &targettypes.OIDCConfig{
Server: "test-server",
CACertificate: []byte("test-cert"),
ServiceAccount: "test-account",
Audience: []string{"test-audience"},
},
}))
})
})
4 changes: 4 additions & 0 deletions apis/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.4
require (
github.com/gardener/component-spec/bindings-go v0.0.98
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.20.1
github.com/onsi/gomega v1.34.2
github.com/robfig/cron/v3 v3.0.1
k8s.io/api v0.30.3
Expand All @@ -24,11 +25,13 @@ require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand All @@ -41,6 +44,7 @@ require (
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
Expand Down
1 change: 0 additions & 1 deletion apis/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDsl
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Expand Down
65 changes: 53 additions & 12 deletions pkg/deployer/lib/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"fmt"
"reflect"

authenticationv1 "k8s.io/api/authentication/v1"
"k8s.io/utils/ptr"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -37,24 +40,62 @@ import (
"github.com/gardener/landscaper/pkg/utils/read_write_layer"
)

func GetRestConfigAndClientAndClientSet(ctx context.Context, resolvedTarget *lsv1alpha1.ResolvedTarget, lsUncachedClient client.Client) (*rest.Config, client.Client, kubernetes.Interface, error) {
func GetRestConfigAndClientAndClientSet(ctx context.Context, resolvedTarget *lsv1alpha1.ResolvedTarget, lsUncachedClient client.Client) (_ *rest.Config, _ client.Client, _ kubernetes.Interface, err error) {
var restConfig *rest.Config

if resolvedTarget.Target == nil {
return nil, nil, nil, fmt.Errorf("resolved target does not contain the original target")
}

targetConfig := &targettypes.KubernetesClusterTargetConfig{}
if err := yaml.Unmarshal([]byte(resolvedTarget.Content), targetConfig); err != nil {
return nil, nil, nil, fmt.Errorf("unable to parse target confíguration: %w", err)
}

kubeconfigBytes, err := GetKubeconfigFromTargetConfig(ctx, targetConfig, resolvedTarget.Namespace, lsUncachedClient)
if err != nil {
return nil, nil, nil, err
}
if targetConfig.Kubeconfig.StrVal != nil || targetConfig.Kubeconfig.SecretRef != nil {
kubeconfigBytes, err := GetKubeconfigFromTargetConfig(ctx, targetConfig, resolvedTarget.Namespace, lsUncachedClient)
if err != nil {
return nil, nil, nil, err
}

kubeconfig, err := clientcmd.NewClientConfigFromBytes(kubeconfigBytes)
if err != nil {
return nil, nil, nil, err
}
restConfig, err := kubeconfig.ClientConfig()
if err != nil {
return nil, nil, nil, err
kubeconfig, err := clientcmd.NewClientConfigFromBytes(kubeconfigBytes)
if err != nil {
return nil, nil, nil, err
}

restConfig, err = kubeconfig.ClientConfig()
if err != nil {
return nil, nil, nil, err
}

} else if targetConfig.OIDCConfig != nil {
serviceAccount := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: resolvedTarget.Namespace,
Name: targetConfig.OIDCConfig.ServiceAccount,
},
}
tokenRequest := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: targetConfig.OIDCConfig.Audience,
ExpirationSeconds: ptr.To[int64](86400),
},
}

if err = lsUncachedClient.SubResource("token").Create(ctx, serviceAccount, tokenRequest); err != nil {
return nil, nil, nil, fmt.Errorf("unable to create token: %w", err)
}

restConfig = &rest.Config{
Host: targetConfig.OIDCConfig.Server,
BearerToken: tokenRequest.Status.Token,
TLSClientConfig: rest.TLSClientConfig{
CAData: targetConfig.OIDCConfig.CACertificate,
},
}

} else {
return nil, nil, nil, fmt.Errorf("unable build rest config from resolved target")
}

kubeClient, err := client.New(restConfig, client.Options{})
Expand Down

0 comments on commit 562969f

Please sign in to comment.