mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(k8s): automatic service account token creation for k8s auth
This commit is contained in:
@@ -49,6 +49,14 @@ type GenericKubernetesAuth struct {
|
|||||||
IdentityID string `json:"identityId"`
|
IdentityID string `json:"identityId"`
|
||||||
// +kubebuilder:validation:Required
|
// +kubebuilder:validation:Required
|
||||||
ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"`
|
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 {
|
type TLSConfig struct {
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ type KubernetesAuthDetails struct {
|
|||||||
|
|
||||||
// +kubebuilder:validation:Required
|
// +kubebuilder:validation:Required
|
||||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
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 {
|
type KubernetesServiceAccountRef struct {
|
||||||
|
|||||||
@@ -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="",resources=configmaps,verbs=get;list;watch;create;update;delete
|
||||||
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update
|
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update
|
||||||
// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch
|
// +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) {
|
func (r *InfisicalDynamicSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||||
|
|
||||||
|
|||||||
@@ -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="",resources=configmaps,verbs=get;list;watch;create;update;delete
|
||||||
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update
|
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update
|
||||||
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch
|
//+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
|
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||||
// move the current state of the cluster closer to the desired state.
|
// move the current state of the cluster closer to the desired state.
|
||||||
|
|||||||
@@ -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="",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=apps,resources=deployments;daemonsets;statefulsets,verbs=list;watch;get;update
|
||||||
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch
|
//+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
|
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||||
// move the current state of the cluster closer to the desired state.
|
// move the current state of the cluster closer to the desired state.
|
||||||
|
|||||||
@@ -8,12 +8,50 @@ import (
|
|||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
|
||||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||||
|
"github.com/aws/smithy-go/ptr"
|
||||||
infisicalSdk "github.com/infisical/go-sdk"
|
infisicalSdk "github.com/infisical/go-sdk"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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{}
|
serviceAccount := &corev1.ServiceAccount{}
|
||||||
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: serviceAccountName, Namespace: namespace}, 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,
|
Namespace: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Namespace,
|
||||||
Name: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Name,
|
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:
|
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,
|
Namespace: infisicalDynamicSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Namespace,
|
||||||
Name: infisicalDynamicSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Name,
|
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
|
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 {
|
if err != nil {
|
||||||
return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err)
|
return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"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"
|
"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
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user