diff --git a/k8-operator/api/v1alpha1/common.go b/k8-operator/api/v1alpha1/common.go index 2489b0b95..3c94b8ab0 100644 --- a/k8-operator/api/v1alpha1/common.go +++ b/k8-operator/api/v1alpha1/common.go @@ -49,6 +49,14 @@ type GenericKubernetesAuth struct { IdentityID string `json:"identityId"` // +kubebuilder:validation:Required ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"` + + // Optionally automatically create a service account token for the configured service account. + // If this is set to `true`, the operator will automatically create a service account token for the configured service account. + // +kubebuilder:validation:Optional + AutoCreateServiceAccountToken bool `json:"autoCreateServiceAccountToken"` + // The audiences to use for the service account token. This is only relevant if `autoCreateServiceAccountToken` is true. + // +kubebuilder:validation:Optional + ServiceAccountTokenAudiences []string `json:"serviceAccountTokenAudiences"` } type TLSConfig struct { diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index e90c06938..ff26a878c 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -38,6 +38,14 @@ type KubernetesAuthDetails struct { // +kubebuilder:validation:Required SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"` + + // Optionally automatically create a service account token for the configured service account. + // If this is set to `true`, the operator will automatically create a service account token for the configured service account. + // +kubebuilder:validation:Optional + AutoCreateServiceAccountToken bool `json:"autoCreateServiceAccountToken"` + // The audiences to use for the service account token. This is only relevant if `autoCreateServiceAccountToken` is true. + // +kubebuilder:validation:Optional + ServiceAccountTokenAudiences []string `json:"serviceAccountTokenAudiences"` } type KubernetesServiceAccountRef struct { diff --git a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go index 5d2470067..a65676739 100644 --- a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go +++ b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go @@ -45,6 +45,9 @@ func (r *InfisicalDynamicSecretReconciler) GetLogger(req ctrl.Request) logr.Logg // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update // +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list +//+kubebuilder:rbac:groups="authentication.k8s.io",resources=tokenreviews,verbs=create +//+kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create func (r *InfisicalDynamicSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index a5526bd6c..ebf537a63 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -48,6 +48,9 @@ func (r *InfisicalPushSecretReconciler) GetLogger(req ctrl.Request) logr.Logger //+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;delete //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update //+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list +//+kubebuilder:rbac:groups="authentication.k8s.io",resources=tokenreviews,verbs=create +//+kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go index 301f3856f..bf7d75830 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go @@ -44,6 +44,9 @@ func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { //+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;delete //+kubebuilder:rbac:groups=apps,resources=deployments;daemonsets;statefulsets,verbs=list;watch;get;update //+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list +//+kubebuilder:rbac:groups="authentication.k8s.io",resources=tokenreviews,verbs=create +//+kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. diff --git a/k8-operator/packages/util/auth.go b/k8-operator/packages/util/auth.go index f4f9348f1..ec4b0f920 100644 --- a/k8-operator/packages/util/auth.go +++ b/k8-operator/packages/util/auth.go @@ -8,12 +8,50 @@ import ( corev1 "k8s.io/api/core/v1" + authenticationv1 "k8s.io/api/authentication/v1" + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/aws/smithy-go/ptr" infisicalSdk "github.com/infisical/go-sdk" "sigs.k8s.io/controller-runtime/pkg/client" ) -func GetServiceAccountToken(k8sClient client.Client, namespace string, serviceAccountName string) (string, error) { +func GetServiceAccountToken(k8sClient client.Client, namespace string, serviceAccountName string, autoCreateServiceAccountToken bool, serviceAccountTokenAudiences []string) (string, error) { + + if autoCreateServiceAccountToken { + if len(serviceAccountTokenAudiences) == 0 { + return "", fmt.Errorf("serviceAccountTokenAudiences is required when autoCreateServiceAccountToken is true") + } + + restClient, err := GetRestClientFromClient() + if err != nil { + return "", fmt.Errorf("failed to get REST client: %w", err) + } + + tokenRequest := &authenticationv1.TokenRequest{ + Spec: authenticationv1.TokenRequestSpec{ + Audiences: serviceAccountTokenAudiences, + ExpirationSeconds: ptr.Int64(600), // 10 minutes. the token only needs to be valid for when we do the initial k8s login. + }, + } + + result := &authenticationv1.TokenRequest{} + err = restClient. + Post(). + Namespace(namespace). + Resource("serviceaccounts"). + Name(serviceAccountName). + SubResource("token"). + Body(tokenRequest). + Do(context.Background()). + Into(result) + + if err != nil { + return "", fmt.Errorf("failed to create token: %w", err) + } + + return result.Status.Token, nil + } serviceAccount := &corev1.ServiceAccount{} err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: serviceAccountName, Namespace: namespace}, serviceAccount) @@ -172,7 +210,9 @@ func HandleKubernetesAuth(ctx context.Context, reconcilerClient client.Client, s Namespace: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Namespace, Name: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Name, }, - SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + AutoCreateServiceAccountToken: infisicalPushSecret.Spec.Authentication.KubernetesAuth.AutoCreateServiceAccountToken, + ServiceAccountTokenAudiences: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountTokenAudiences, } case SecretCrd.INFISICAL_DYNAMIC_SECRET: @@ -188,7 +228,9 @@ func HandleKubernetesAuth(ctx context.Context, reconcilerClient client.Client, s Namespace: infisicalDynamicSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Namespace, Name: infisicalDynamicSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Name, }, - SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + AutoCreateServiceAccountToken: infisicalDynamicSecret.Spec.Authentication.KubernetesAuth.AutoCreateServiceAccountToken, + ServiceAccountTokenAudiences: infisicalDynamicSecret.Spec.Authentication.KubernetesAuth.ServiceAccountTokenAudiences, } } @@ -196,7 +238,14 @@ func HandleKubernetesAuth(ctx context.Context, reconcilerClient client.Client, s return AuthenticationDetails{}, ErrAuthNotApplicable } - serviceAccountToken, err := GetServiceAccountToken(reconcilerClient, kubernetesAuthSpec.ServiceAccountRef.Namespace, kubernetesAuthSpec.ServiceAccountRef.Name) + serviceAccountToken, err := GetServiceAccountToken( + reconcilerClient, + kubernetesAuthSpec.ServiceAccountRef.Namespace, + kubernetesAuthSpec.ServiceAccountRef.Name, + kubernetesAuthSpec.AutoCreateServiceAccountToken, + kubernetesAuthSpec.ServiceAccountTokenAudiences, + ) + if err != nil { return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err) } diff --git a/k8-operator/packages/util/kubernetes.go b/k8-operator/packages/util/kubernetes.go index 75d5d6bc4..b11c0a623 100644 --- a/k8-operator/packages/util/kubernetes.go +++ b/k8-operator/packages/util/kubernetes.go @@ -9,6 +9,9 @@ import ( corev1 "k8s.io/api/core/v1" k8Errors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -58,3 +61,32 @@ func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClie return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil } + +func getKubeClusterConfig() (*rest.Config, error) { + config, err := rest.InClusterConfig() + if err != nil { + + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := &clientcmd.ConfigOverrides{} + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + return kubeConfig.ClientConfig() + } + + return config, nil +} + +func GetRestClientFromClient() (rest.Interface, error) { + + config, err := getKubeClusterConfig() + if err != nil { + return nil, err + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return clientset.CoreV1().RESTClient(), nil + +}