Compare commits

...

6 Commits

Author SHA1 Message Date
efe8ff0e20 Update kubernetes.mdx 2024-05-30 20:29:22 +02:00
538d8fbc76 Helm 2024-05-30 20:03:15 +02:00
57dc038b71 Types 2024-05-30 19:58:09 +02:00
f310130318 Sample 2024-05-30 19:57:58 +02:00
ff0f61f1ff Native K8 auth support 2024-05-30 19:57:17 +02:00
3fce7666ab Feat: native K8 auth support 2024-05-30 19:57:05 +02:00
11 changed files with 217 additions and 8 deletions

View File

@ -83,6 +83,14 @@ spec:
secretName: universal-auth-credentials
secretNamespace: default
kubernetes:
identityId: <your-machine-identity-id>
secretsScope:
projectSlug: test-arou
envSlug: dev # "dev", "staging", "prod", etc..
secretsPath: "/" # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
# Service tokens are deprecated and will be removed in the near future. Please use Machine Identities for authenticating with Infisical.
serviceToken:
serviceTokenSecretReference:
@ -136,8 +144,8 @@ When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
The universal machine identity authentication method is used to authenticate with Infisical. The client ID and client secret needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials.
<Steps>
<Step title="Create a machine identity">
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth).
<Step title="Create a universal auth machine identity">
You need to create a machine identity that uses the Universal Auth method, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth).
</Step>
<Step title="Create Kubernetes secret containing machine identity credentials">
Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials.
@ -187,6 +195,40 @@ spec:
</Accordion>
<Accordion title="authentication.kubernetes">
The native kubernetes machine identity auth method is used to authenticate with Infisical. The identity ID is used to authenticate with Infisical. [Read more about native kubernetes auth](/documentation/platform/identities/kubernetes-auth).
<Steps>
<Step title="Create a universal auth machine identity">
You need to create a machine identity that uses the Kuberenetes Auth method, and give it access to the project(s) you want to interact with. You can [read more about kubernetes auth here](/documentation/platform/identities/kubernetes-auth).
</Step>
<Step title="Add the identity ID to the InfisicalSecret CRD">
Once you have created your machine identity and added it to your project(s), you will need to add the identity ID to the InfisicalSecret CRD.
Make sure you replace `<your-identity-id>` with the identity ID.
```yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: infisicalsecret-sample-crd
spec:
authentication:
kubernetes:
identityId: <your-identity-id> # <-- identity ID
secretsScope:
projectSlug: <project-slug> # <-- project slug
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
...
```
</Step>
</Steps>
</Accordion>
<Accordion title="authentication.serviceToken">
<Warning>
Service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities).

View File

@ -37,6 +37,31 @@ spec:
properties:
authentication:
properties:
kubernetes:
properties:
identityId:
type: string
secretsScope:
properties:
envSlug:
type: string
projectSlug:
type: string
recursive:
type: boolean
secretsPath:
type: string
required:
- envSlug
- projectSlug
- secretsPath
type: object
serviceAccountTokenPath:
type: string
required:
- identityId
- secretsScope
type: object
serviceAccount:
properties:
environmentName:

View File

@ -32,7 +32,7 @@ controllerManager:
- ALL
image:
repository: infisical/kubernetes-operator
tag: v0.5.1 # fixed to prevent accidental upgrade
tag: v0.5.1 # fixed to prevent accidental upgrade.
resources:
limits:
cpu: 500m

View File

@ -11,6 +11,8 @@ type Authentication struct {
ServiceToken ServiceTokenDetails `json:"serviceToken"`
// +kubebuilder:validation:Optional
UniversalAuth UniversalAuthDetails `json:"universalAuth"`
// +kubebuilder:validation:Optional
Kubernetes KubernetesAuthDetails `json:"kubernetes"`
}
type UniversalAuthDetails struct {
@ -20,6 +22,16 @@ type UniversalAuthDetails struct {
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
}
type KubernetesAuthDetails struct {
// +kubebuilder:validation:Required
IdentityId string `json:"identityId"`
// +kubebuilder:validation:Optional
ServiceAccountTokenPath string `json:"serviceAccountTokenPath"`
// +kubebuilder:validation:Required
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
}
type ServiceTokenDetails struct {
// +kubebuilder:validation:Required
ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"`

View File

@ -32,6 +32,7 @@ func (in *Authentication) DeepCopyInto(out *Authentication) {
out.ServiceAccount = in.ServiceAccount
out.ServiceToken = in.ServiceToken
out.UniversalAuth = in.UniversalAuth
out.Kubernetes = in.Kubernetes
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authentication.
@ -158,6 +159,22 @@ func (in *KubeSecretReference) DeepCopy() *KubeSecretReference {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesAuthDetails) DeepCopyInto(out *KubernetesAuthDetails) {
*out = *in
out.SecretsScope = in.SecretsScope
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesAuthDetails.
func (in *KubernetesAuthDetails) DeepCopy() *KubernetesAuthDetails {
if in == nil {
return nil
}
out := new(KubernetesAuthDetails)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineIdentityScopeInWorkspace) DeepCopyInto(out *MachineIdentityScopeInWorkspace) {
*out = *in

View File

@ -37,6 +37,31 @@ spec:
properties:
authentication:
properties:
kubernetes:
properties:
identityId:
type: string
secretsScope:
properties:
envSlug:
type: string
projectSlug:
type: string
recursive:
type: boolean
secretsPath:
type: string
required:
- envSlug
- projectSlug
- secretsPath
type: object
serviceAccountTokenPath:
type: string
required:
- identityId
- secretsScope
type: object
serviceAccount:
properties:
environmentName:

View File

@ -21,6 +21,14 @@ spec:
secretsPath: <secrets-path> # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
kubernetes:
identityId: 2345b5b9-054b-4e88-9c33-a61802bba2c1
secretsScope:
projectSlug: test-arou
envSlug: dev # "dev", "staging", "prod", etc..
secretsPath: "/" # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
universalAuth:
secretsScope:
projectSlug: <project-slug>

View File

@ -34,10 +34,12 @@ var AuthStrategy = struct {
SERVICE_TOKEN AuthStrategyType
SERVICE_ACCOUNT AuthStrategyType
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
KUBERNETES AuthStrategyType
}{
SERVICE_TOKEN: "SERVICE_TOKEN",
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
KUBERNETES: "KUBERNETES",
}
var machineIdentityTokenInstance *util.MachineIdentityToken
@ -135,7 +137,6 @@ func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthFromKubeSecret(ctx
clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET]
return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil
}
// Fetches service account credentials from a Kubernetes secret specified in the infisicalSecret object, extracts the access key, public key, and private key from the secret, and returns them as a ServiceAccountCredentials object.
@ -268,6 +269,8 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
authStrategy = AuthStrategy.SERVICE_TOKEN
} else if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" {
authStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
} else if infisicalSecret.Spec.Authentication.Kubernetes.IdentityId != "" {
authStrategy = AuthStrategy.KUBERNETES
} else {
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets\n")
}
@ -296,6 +299,13 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY && machineIdentityTokenInstance == nil {
// Create new machine identity token instance
machineIdentityTokenInstance = util.NewMachineIdentityToken(infisicalMachineIdentityCreds.ClientId, infisicalMachineIdentityCreds.ClientSecret)
} else if authStrategy == AuthStrategy.KUBERNETES && machineIdentityTokenInstance == nil {
// Create new machine identity token instance
machineIdentityTokenInstance, err = util.NewMachineIdentityKubernetesToken(infisicalSecret.Spec.Authentication.Kubernetes.IdentityId, infisicalSecret.Spec.Authentication.Kubernetes.ServiceAccountTokenPath)
if err != nil {
return fmt.Errorf("ReconcileInfisicalSecret: unable to create machine identity token instance from kubernetes auth [err=%s]", err)
}
}
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
@ -320,20 +330,29 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
}
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service token")
} else if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY { // Machine Identity
} else if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY || authStrategy == AuthStrategy.KUBERNETES { // Machine Identity
accessToken, err := machineIdentityTokenInstance.GetToken()
if err != nil {
return fmt.Errorf("%s", "Waiting for access token to become available")
}
scope := infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope
var scope v1alpha1.MachineIdentityScopeInWorkspace
var formattedStrategy string
if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
scope = infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope
formattedStrategy = "universal auth"
} else {
scope = infisicalSecret.Spec.Authentication.Kubernetes.SecretsScope
formattedStrategy = "kubernetes auth"
}
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaUniversalAuth(accessToken, secretVersionBasedOnETag, scope)
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via universal auth")
fmt.Printf("ReconcileInfisicalSecret: Fetched secrets via %s\n", formattedStrategy)
} else {
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets")

View File

@ -218,3 +218,23 @@ func CallGetServiceAccountKeysV2(httpClient *resty.Client, request GetServiceAcc
return serviceAccountKeysResponse, nil
}
func CallKubernetesAuthLogin(request KubernetesAuthLoginRequest) (KubernetesAuthLoginResponse, error) {
var kubernetesAuthLoginResponse KubernetesAuthLoginResponse
response, err := resty.New().
R().
SetResult(&kubernetesAuthLoginResponse).
SetBody(request).
Post(fmt.Sprintf("%v/v1/auth/kubernetes-auth/login", API_HOST_URL))
if err != nil {
return KubernetesAuthLoginResponse{}, fmt.Errorf("CallKubernetesAuthLogin: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return KubernetesAuthLoginResponse{}, fmt.Errorf("CallKubernetesAuthLogin: Unsuccessful response: [response=%s]", response)
}
return kubernetesAuthLoginResponse, nil
}

View File

@ -170,6 +170,15 @@ type MachineIdentityUniversalAuthRefreshRequest struct {
AccessToken string `json:"accessToken"`
}
type KubernetesAuthLoginRequest struct {
IdentityId string `json:"identityId"`
Jwt string `json:"jwt"`
}
type KubernetesAuthLoginResponse struct {
AccessToken string `json:"accessToken"`
}
type ServiceAccountKey struct {
ID string `json:"_id"`
EncryptedKey string `json:"encryptedKey"`

View File

@ -35,6 +35,38 @@ func NewMachineIdentityToken(clientId string, clientSecret string) *MachineIdent
return &token
}
func NewMachineIdentityKubernetesToken(identityId string, serviceAccountTokenPath string) (*MachineIdentityToken, error) {
if serviceAccountTokenPath == "" {
serviceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
}
// Get the token from the service account token path using the os package
kubernetesJwtToken, err := os.ReadFile(serviceAccountTokenPath)
if err != nil {
return nil, fmt.Errorf("NewMachineIdentityKubernetesToken: Unable to read service account token file [err=%s]", err)
}
res, err := api.CallKubernetesAuthLogin(api.KubernetesAuthLoginRequest{
IdentityId: identityId,
Jwt: string(kubernetesJwtToken),
})
if err != nil {
return nil, err
}
// We don't handle token lifecycle for kubernetes tokens.
// We re-use the same structure for kubernetes tokens to keep the code simple and to have a place to store the token.
// Now we can call the GetToken method to get the token from within the operator.
token := MachineIdentityToken{
accessToken: res.AccessToken,
}
return &token, nil
}
func (t *MachineIdentityToken) HandleTokenLifecycle() error {
for {