Merge pull request #1426 from fluxcd/rfc-0010
[RFC-0010] Introduce object-level workload identity for KMS decryption
This commit is contained in:
commit
d775ed3a19
|
|
@ -205,7 +205,18 @@ type Decryption struct {
|
|||
// +required
|
||||
Provider string `json:"provider"`
|
||||
|
||||
// ServiceAccountName is the name of the service account used to
|
||||
// authenticate with KMS services from cloud providers. If a
|
||||
// static credential for a given cloud provider is defined
|
||||
// inside the Secret referenced by SecretRef, that static
|
||||
// credential takes priority.
|
||||
// +optional
|
||||
ServiceAccountName string `json:"serviceAccountName,omitempty"`
|
||||
|
||||
// The secret name containing the private OpenPGP keys used for decryption.
|
||||
// A static credential for a cloud provider defined inside the Secret
|
||||
// takes priority to secret-less authentication with the ServiceAccountName
|
||||
// field.
|
||||
// +optional
|
||||
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,8 +86,11 @@ spec:
|
|||
- sops
|
||||
type: string
|
||||
secretRef:
|
||||
description: The secret name containing the private OpenPGP keys
|
||||
used for decryption.
|
||||
description: |-
|
||||
The secret name containing the private OpenPGP keys used for decryption.
|
||||
A static credential for a cloud provider defined inside the Secret
|
||||
takes priority to secret-less authentication with the ServiceAccountName
|
||||
field.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
|
|
@ -95,6 +98,14 @@ spec:
|
|||
required:
|
||||
- name
|
||||
type: object
|
||||
serviceAccountName:
|
||||
description: |-
|
||||
ServiceAccountName is the name of the service account used to
|
||||
authenticate with KMS services from cloud providers. If a
|
||||
static credential for a given cloud provider is defined
|
||||
inside the Secret referenced by SecretRef, that static
|
||||
credential takes priority.
|
||||
type: string
|
||||
required:
|
||||
- provider
|
||||
type: object
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ rules:
|
|||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts/token
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- kustomize.toolkit.fluxcd.io
|
||||
resources:
|
||||
|
|
|
|||
|
|
@ -574,6 +574,22 @@ string
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>serviceAccountName</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ServiceAccountName is the name of the service account used to
|
||||
authenticate with KMS services from cloud providers. If a
|
||||
static credential for a given cloud provider is defined
|
||||
inside the Secret referenced by SecretRef, that static
|
||||
credential takes priority.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
|
|
@ -583,7 +599,10 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
|||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>The secret name containing the private OpenPGP keys used for decryption.</p>
|
||||
<p>The secret name containing the private OpenPGP keys used for decryption.
|
||||
A static credential for a cloud provider defined inside the Secret
|
||||
takes priority to secret-less authentication with the ServiceAccountName
|
||||
field.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -823,33 +823,46 @@ For more information, see [remote clusters/Cluster-API](#remote-clusterscluster-
|
|||
|
||||
### Decryption
|
||||
|
||||
`.spec.decryption` is an optional field to specify the configuration to decrypt
|
||||
Secrets, ConfigMaps and patches that are a part of the Kustomization.
|
||||
Storing Secrets in Git repositories in plain text or base64 is unsafe,
|
||||
regardless of the visibility or access restrictions of the repository.
|
||||
|
||||
Since Secrets are either plain text or `base64` encoded, it's unsafe to store
|
||||
them in plain text in a public or private Git repository. In order to store
|
||||
them safely, you can use [Mozilla SOPS](https://github.com/mozilla/sops) and
|
||||
encrypt your Kubernetes Secret data with [age](https://age-encryption.org/v1/)
|
||||
and/or [OpenPGP](https://www.openpgp.org) keys, or with provider implementations
|
||||
like Azure Key Vault, GCP KMS or Hashicorp Vault.
|
||||
In order to store Secrets safely in Git repositorioes you can use an
|
||||
encryption provider and the optional field `.spec.decryption` to
|
||||
configure decryption for Secrets that are a part of the Kustomization.
|
||||
|
||||
Also, you may want to encrypt some parts of resources as well. In order to do that,
|
||||
you may encrypt patches as well.
|
||||
The only supported encryption provider is [SOPS](https://getsops.io/).
|
||||
With SOPS you can encrypt your secrets with [age](https://github.com/FiloSottile/age)
|
||||
or [OpenPGP](https://www.openpgp.org) keys, or with keys from Key Management Services
|
||||
(KMS), like AWS KMS, Azure Key Vault, GCP KMS or Hashicorp Vault.
|
||||
|
||||
**Note:** You must leave `metadata`, `kind` or `apiVersion` in plain text.
|
||||
An easy way to do this is to limit encrypted keys by appending `--encrypted-regex '^(data|stringData)$'`
|
||||
to your `sops --encrypt` command.
|
||||
An easy way to do this is limiting the encrypted keys with the flag
|
||||
`--encrypted-regex '^(data|stringData)$'` in your `sops encrypt` command.
|
||||
|
||||
It has two fields:
|
||||
The `.spec.decryption` field has the following subfields:
|
||||
|
||||
- `.provider`: The secrets decryption provider to be used. This field is required and
|
||||
the only supported value is `sops`.
|
||||
- `.secretRef.name`: The name of the secret that contains the keys to be used for
|
||||
decryption. This field can be omitted when using the
|
||||
[global decryption](#controller-global-decryption) option.
|
||||
- `.secretRef.name`: The name of the secret that contains the keys or cloud provider
|
||||
static credentials for KMS services to be used for decryption.
|
||||
- `.serviceAccountName`: The name of the service account used for
|
||||
secret-less authentication with KMS services from cloud providers.
|
||||
See the [workload identity](/flux/installation/configuration/workload-identity/) docs
|
||||
for how to configure a cloud provider identity for this service account.
|
||||
|
||||
If a static credential for a given cloud provider is defined inside the secret
|
||||
referenced by `.secretRef`, that static credential takes priority over secret-less
|
||||
authentication for that provider. If no static credentials are defined for a given
|
||||
cloud provider inside the secret, secret-less authentication is attempted for that
|
||||
provider.
|
||||
|
||||
If `.serviceAccountName` is specified for secret-less authentication,
|
||||
it takes priority over [controller global decryption](#controller-global-decryption)
|
||||
for all cloud providers.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
|
|
@ -863,13 +876,11 @@ spec:
|
|||
name: repository-with-secrets
|
||||
decryption:
|
||||
provider: sops
|
||||
serviceAccountName: sops-identity
|
||||
secretRef:
|
||||
name: sops-keys
|
||||
name: sops-keys-and-credentials
|
||||
```
|
||||
|
||||
**Note:** For information on Secrets decryption at a controller level, please
|
||||
refer to [controller global decryption](#controller-global-decryption).
|
||||
|
||||
The Secret's `.data` section is expected to contain entries with decryption
|
||||
keys (for age and OpenPGP), or credentials (for any of the supported provider
|
||||
implementations). The controller identifies the type of the entry by the suffix
|
||||
|
|
@ -880,7 +891,7 @@ of the key (e.g. `.agekey`), or a fixed key (e.g. `sops.vault-token`).
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: sops-keys
|
||||
name: sops-keys-and-credentials
|
||||
namespace: default
|
||||
data:
|
||||
# Exemplary age private key
|
||||
|
|
@ -937,9 +948,9 @@ metadata:
|
|||
namespace: default
|
||||
data:
|
||||
sops.aws-kms: |
|
||||
aws_access_key_id: some-access-key-id
|
||||
aws_secret_access_key: some-aws-secret-access-key
|
||||
aws_session_token: some-aws-session-token # this field is optional
|
||||
aws_access_key_id: some-access-key-id
|
||||
aws_secret_access_key: some-aws-secret-access-key
|
||||
aws_session_token: some-aws-session-token # this field is optional
|
||||
```
|
||||
|
||||
#### Azure Key Vault Secret entry
|
||||
|
|
@ -1408,6 +1419,8 @@ it is possible to specify global decryption settings on the
|
|||
kustomize-controller Pod. When the controller fails to find credentials on the
|
||||
Kustomization object itself, it will fall back to these defaults.
|
||||
|
||||
See also the [workload identity](/flux/installation/configuration/workload-identity/) docs.
|
||||
|
||||
#### AWS KMS
|
||||
|
||||
While making use of the [IAM OIDC provider](https://eksctl.io/usage/iamserviceaccounts/)
|
||||
|
|
|
|||
11
go.mod
11
go.mod
|
|
@ -9,10 +9,12 @@ replace github.com/fluxcd/kustomize-controller/api => ./api
|
|||
replace github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.1-0.20220411205349-bde1400a84be
|
||||
|
||||
require (
|
||||
cloud.google.com/go/kms v1.21.2
|
||||
filippo.io/age v1.2.1
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/dimchansky/utfbom v1.1.1
|
||||
|
|
@ -22,6 +24,8 @@ require (
|
|||
github.com/fluxcd/pkg/apis/event v0.17.0
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.10.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.11.0
|
||||
github.com/fluxcd/pkg/auth v0.12.0
|
||||
github.com/fluxcd/pkg/cache v0.9.0
|
||||
github.com/fluxcd/pkg/http/fetch v0.16.0
|
||||
github.com/fluxcd/pkg/kustomize v1.17.0
|
||||
github.com/fluxcd/pkg/runtime v0.59.0
|
||||
|
|
@ -36,6 +40,7 @@ require (
|
|||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/spf13/pflag v1.0.6
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/oauth2 v0.29.0
|
||||
k8s.io/api v0.33.0
|
||||
k8s.io/apimachinery v0.33.0
|
||||
k8s.io/client-go v0.33.0
|
||||
|
|
@ -61,7 +66,6 @@ require (
|
|||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/kms v1.21.2 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/storage v1.51.0 // indirect
|
||||
|
|
@ -80,7 +84,6 @@ require (
|
|||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.2.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
|
|
@ -89,6 +92,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
|
|
@ -112,6 +116,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v28.1.1+incompatible // indirect
|
||||
github.com/docker/docker v28.1.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
|
|
@ -144,6 +149,7 @@ require (
|
|||
github.com/google/cel-go v0.23.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-containerregistry v0.20.3 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
|
|
@ -222,7 +228,6 @@ require (
|
|||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
|
|
|
|||
12
go.sum
12
go.sum
|
|
@ -91,6 +91,8 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d
|
|||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3 h1:YyH8Hk73bYzdbvf6S8NF5z/fb/1stpiMnFSfL6jSfRA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3/go.mod h1:iQ1skgw1XRK+6Lgkb0I9ODatAP72WoTILh0zXQ5DtbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
|
||||
|
|
@ -129,6 +131,8 @@ github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73l
|
|||
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
|
|
@ -148,6 +152,8 @@ github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5M
|
|||
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
|
|
@ -182,6 +188,10 @@ github.com/fluxcd/pkg/apis/kustomize v1.10.0 h1:47EeSzkQvlQZdH92vHMe2lK2iR8aOSEJ
|
|||
github.com/fluxcd/pkg/apis/kustomize v1.10.0/go.mod h1:UsqMV4sqNa1Yg0pmTsdkHRJr7bafBOENIJoAN+3ezaQ=
|
||||
github.com/fluxcd/pkg/apis/meta v1.11.0 h1:h8q95k6ZEK1HCfsLkt8Np3i6ktb6ZzcWJ6hg++oc9w0=
|
||||
github.com/fluxcd/pkg/apis/meta v1.11.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
|
||||
github.com/fluxcd/pkg/auth v0.12.0 h1:35o0ziYMLZVgJwNvJBGsv/wd903B2fMagcrnm1ptUjc=
|
||||
github.com/fluxcd/pkg/auth v0.12.0/go.mod h1:gQD2VT5OhIR1E8ZTEsTaho3bDQZidr9P10smH/awcew=
|
||||
github.com/fluxcd/pkg/cache v0.9.0 h1:EGKfOLMG3fOwWnH/4Axl5xd425mxoQbZzlZoLfd8PDk=
|
||||
github.com/fluxcd/pkg/cache v0.9.0/go.mod h1:jMwabjWfsC5lW8hE7NM3wtGNwSJ38Javx6EKbEi7INU=
|
||||
github.com/fluxcd/pkg/envsubst v1.4.0 h1:pYsb6wrmXOSfHXuXQHaaBBMt3LumhgCb8SMdBNAwV/U=
|
||||
github.com/fluxcd/pkg/envsubst v1.4.0/go.mod h1:zSDFO3Wawi+vI2NPxsMQp+EkIsz/85MNg/s1Wzmqt+s=
|
||||
github.com/fluxcd/pkg/http/fetch v0.16.0 h1:XzhBTSK5HNdAPEnEGMJHwtoN2LfqQ9QFDsu3DGzl908=
|
||||
|
|
@ -254,6 +264,8 @@ github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcb
|
|||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
|
||||
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package intcache
|
||||
|
||||
const (
|
||||
OperationDecryptWithAWS = "decrypt_with_aws"
|
||||
OperationDecryptWithAzure = "decrypt_with_azure"
|
||||
OperationDecryptWithGCP = "decrypt_with_gcp"
|
||||
)
|
||||
|
||||
var AllOperations = []string{
|
||||
OperationDecryptWithAWS,
|
||||
OperationDecryptWithAzure,
|
||||
OperationDecryptWithGCP,
|
||||
}
|
||||
|
|
@ -27,8 +27,6 @@ import (
|
|||
"time"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/fluxcd/pkg/ssa/normalize"
|
||||
ssautil "github.com/fluxcd/pkg/ssa/utils"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
|
|
@ -54,6 +52,7 @@ import (
|
|||
apiacl "github.com/fluxcd/pkg/apis/acl"
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/http/fetch"
|
||||
generator "github.com/fluxcd/pkg/kustomize"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
|
|
@ -66,11 +65,14 @@ import (
|
|||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
"github.com/fluxcd/pkg/runtime/statusreaders"
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
"github.com/fluxcd/pkg/ssa/normalize"
|
||||
ssautil "github.com/fluxcd/pkg/ssa/utils"
|
||||
"github.com/fluxcd/pkg/tar"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
intcache "github.com/fluxcd/kustomize-controller/internal/cache"
|
||||
"github.com/fluxcd/kustomize-controller/internal/decryptor"
|
||||
"github.com/fluxcd/kustomize-controller/internal/inventory"
|
||||
)
|
||||
|
|
@ -81,6 +83,7 @@ import (
|
|||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=buckets;ocirepositories;gitrepositories,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=buckets/status;ocirepositories/status;gitrepositories/status,verbs=get
|
||||
// +kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create
|
||||
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
|
||||
|
||||
// KustomizationReconciler reconciles a Kustomization object
|
||||
|
|
@ -106,6 +109,7 @@ type KustomizationReconciler struct {
|
|||
DisallowedFieldManagers []string
|
||||
StrictSubstitutions bool
|
||||
GroupChangeLog bool
|
||||
TokenCache *cache.TokenCache
|
||||
}
|
||||
|
||||
// KustomizationReconcilerOptions contains options for the KustomizationReconciler.
|
||||
|
|
@ -626,17 +630,20 @@ func (r *KustomizationReconciler) generate(obj unstructured.Unstructured,
|
|||
func (r *KustomizationReconciler) build(ctx context.Context,
|
||||
obj *kustomizev1.Kustomization, u unstructured.Unstructured,
|
||||
workDir, dirPath string) ([]byte, error) {
|
||||
dec, cleanup, err := decryptor.NewTempDecryptor(workDir, r.Client, obj)
|
||||
dec, cleanup, err := decryptor.NewTempDecryptor(workDir, r.Client, obj, r.TokenCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Import decryption keys
|
||||
// Import keys and static credentials for decryption.
|
||||
if err := dec.ImportKeys(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set options for secret-less authentication with cloud providers for decryption.
|
||||
dec.SetAuthOptions(ctx)
|
||||
|
||||
// Decrypt Kustomize EnvSources files before build
|
||||
if err = dec.DecryptSources(dirPath); err != nil {
|
||||
return nil, fmt.Errorf("error decrypting sources: %w", err)
|
||||
|
|
@ -1090,6 +1097,12 @@ func (r *KustomizationReconciler) finalize(ctx context.Context,
|
|||
|
||||
// Remove our finalizer from the list and update it
|
||||
controllerutil.RemoveFinalizer(obj, kustomizev1.KustomizationFinalizer)
|
||||
|
||||
// Cleanup caches.
|
||||
for _, op := range intcache.AllOperations {
|
||||
r.TokenCache.DeleteEventsForObject(kustomizev1.KustomizationKind, obj.GetName(), obj.GetNamespace(), op)
|
||||
}
|
||||
|
||||
// Stop reconciliation as the object is being deleted
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,25 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
gcpkmsapi "cloud.google.com/go/kms/apiv1"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
awssdk "github.com/aws/aws-sdk-go-v2/aws"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
"github.com/fluxcd/pkg/auth/aws"
|
||||
"github.com/fluxcd/pkg/auth/azure"
|
||||
"github.com/fluxcd/pkg/auth/gcp"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/getsops/sops/v3/aes"
|
||||
"github.com/getsops/sops/v3/age"
|
||||
"github.com/getsops/sops/v3/azkv"
|
||||
"github.com/getsops/sops/v3/cmd/sops/common"
|
||||
"github.com/getsops/sops/v3/cmd/sops/formats"
|
||||
"github.com/getsops/sops/v3/config"
|
||||
"github.com/getsops/sops/v3/keyservice"
|
||||
awskms "github.com/getsops/sops/v3/kms"
|
||||
"github.com/getsops/sops/v3/pgp"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -51,6 +59,7 @@ import (
|
|||
"sigs.k8s.io/yaml"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
intcache "github.com/fluxcd/kustomize-controller/internal/cache"
|
||||
intawskms "github.com/fluxcd/kustomize-controller/internal/sops/awskms"
|
||||
intazkv "github.com/fluxcd/kustomize-controller/internal/sops/azkv"
|
||||
intkeyservice "github.com/fluxcd/kustomize-controller/internal/sops/keyservice"
|
||||
|
|
@ -127,6 +136,8 @@ type Decryptor struct {
|
|||
// injected into most resources, causing the integrity check to fail.
|
||||
// Mostly kept around for feature completeness and documentation purposes.
|
||||
checkSopsMac bool
|
||||
// tokenCache is the cache for token credentials.
|
||||
tokenCache *cache.TokenCache
|
||||
|
||||
// gnuPGHome is the absolute path of the GnuPG home directory used to
|
||||
// decrypt PGP data. When empty, the systems' GnuPG keyring is used.
|
||||
|
|
@ -137,15 +148,15 @@ type Decryptor struct {
|
|||
// vaultToken is the Hashicorp Vault token used to authenticate towards
|
||||
// any Vault server.
|
||||
vaultToken string
|
||||
// awsCredsProvider is the AWS credentials provider object used to authenticate
|
||||
// awsCredentialsProvider is the AWS credentials provider object used to authenticate
|
||||
// towards any AWS KMS.
|
||||
awsCredsProvider *awskms.CredentialsProvider
|
||||
// azureToken is the Azure credential token used to authenticate towards
|
||||
awsCredentialsProvider func(region string) awssdk.CredentialsProvider
|
||||
// azureTokenCredential is the Azure credential token used to authenticate towards
|
||||
// any Azure Key Vault.
|
||||
azureToken *azkv.TokenCredential
|
||||
// gcpCredsJSON is the JSON credential file of the service account used to
|
||||
// authenticate towards any GCP KMS.
|
||||
gcpCredsJSON []byte
|
||||
azureTokenCredential azcore.TokenCredential
|
||||
// gcpTokenSource is the GCP token source used to authenticate towards
|
||||
// any GCP KMS.
|
||||
gcpTokenSource oauth2.TokenSource
|
||||
|
||||
// keyServices are the SOPS keyservice.KeyServiceClient's available to the
|
||||
// decryptor.
|
||||
|
|
@ -155,25 +166,28 @@ type Decryptor struct {
|
|||
|
||||
// NewDecryptor creates a new Decryptor for the given kustomization.
|
||||
// gnuPGHome can be empty, in which case the systems' keyring is used.
|
||||
func NewDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization, maxFileSize int64, gnuPGHome string) *Decryptor {
|
||||
func NewDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization,
|
||||
maxFileSize int64, gnuPGHome string, tokenCache *cache.TokenCache) *Decryptor {
|
||||
return &Decryptor{
|
||||
root: root,
|
||||
client: client,
|
||||
kustomization: kustomization,
|
||||
maxFileSize: maxFileSize,
|
||||
gnuPGHome: pgp.GnuPGHome(gnuPGHome),
|
||||
tokenCache: tokenCache,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTempDecryptor creates a new Decryptor, with a temporary GnuPG
|
||||
// home directory to Decryptor.ImportKeys() into.
|
||||
func NewTempDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization) (*Decryptor, func(), error) {
|
||||
func NewTempDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization,
|
||||
tokenCache *cache.TokenCache) (*Decryptor, func(), error) {
|
||||
gnuPGHome, err := pgp.NewGnuPGHome()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot create decryptor: %w", err)
|
||||
}
|
||||
cleanup := func() { _ = os.RemoveAll(gnuPGHome.String()) }
|
||||
return NewDecryptor(root, client, kustomization, maxEncryptedFileSize, gnuPGHome.String()), cleanup, nil
|
||||
return NewDecryptor(root, client, kustomization, maxEncryptedFileSize, gnuPGHome.String(), tokenCache), cleanup, nil
|
||||
}
|
||||
|
||||
// IsEncryptedSecret checks if the given object is a Kubernetes Secret encrypted
|
||||
|
|
@ -228,7 +242,6 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
|
|||
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
|
||||
}
|
||||
case filepath.Ext(DecryptionVaultTokenFileName):
|
||||
// Make sure we have the absolute name
|
||||
if name == DecryptionVaultTokenFileName {
|
||||
token := string(value)
|
||||
token = strings.Trim(strings.TrimSpace(token), "\n")
|
||||
|
|
@ -240,10 +253,9 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
|
||||
}
|
||||
d.awsCredsProvider = awskms.NewCredentialsProvider(awsCreds)
|
||||
d.awsCredentialsProvider = func(string) awssdk.CredentialsProvider { return awsCreds }
|
||||
}
|
||||
case filepath.Ext(DecryptionAzureAuthFile):
|
||||
// Make sure we have the absolute name
|
||||
if name == DecryptionAzureAuthFile {
|
||||
conf := intazkv.AADConfig{}
|
||||
if err = intazkv.LoadAADConfigFromBytes(value, &conf); err != nil {
|
||||
|
|
@ -253,11 +265,16 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
|
||||
}
|
||||
d.azureToken = azkv.NewTokenCredential(azureToken)
|
||||
d.azureTokenCredential = azureToken
|
||||
}
|
||||
case filepath.Ext(DecryptionGCPCredsFile):
|
||||
if name == DecryptionGCPCredsFile {
|
||||
d.gcpCredsJSON = bytes.Trim(value, "\n")
|
||||
creds, err := google.CredentialsFromJSON(ctx,
|
||||
bytes.Trim(value, "\n"), gcpkmsapi.DefaultAuthScopes()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
|
||||
}
|
||||
d.gcpTokenSource = creds.TokenSource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -265,6 +282,63 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetAuthOptions sets the authentication options for secret-less authentication
|
||||
// with cloud providers.
|
||||
func (d *Decryptor) SetAuthOptions(ctx context.Context) {
|
||||
if d.kustomization.Spec.Decryption == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch d.kustomization.Spec.Decryption.Provider {
|
||||
case DecryptionProviderSOPS:
|
||||
var opts []auth.Option
|
||||
|
||||
if d.kustomization.Spec.Decryption.ServiceAccountName != "" {
|
||||
serviceAccount := types.NamespacedName{
|
||||
Name: d.kustomization.Spec.Decryption.ServiceAccountName,
|
||||
Namespace: d.kustomization.GetNamespace(),
|
||||
}
|
||||
opts = append(opts, auth.WithServiceAccount(serviceAccount, d.client))
|
||||
}
|
||||
|
||||
involvedObject := cache.InvolvedObject{
|
||||
Kind: kustomizev1.KustomizationKind,
|
||||
Name: d.kustomization.GetName(),
|
||||
Namespace: d.kustomization.GetNamespace(),
|
||||
}
|
||||
|
||||
if d.awsCredentialsProvider == nil {
|
||||
awsOpts := opts
|
||||
if d.tokenCache != nil {
|
||||
involvedObject.Operation = intcache.OperationDecryptWithAWS
|
||||
awsOpts = append(awsOpts, auth.WithCache(*d.tokenCache, involvedObject))
|
||||
}
|
||||
d.awsCredentialsProvider = func(region string) awssdk.CredentialsProvider {
|
||||
awsOpts := append(awsOpts, auth.WithSTSRegion(region))
|
||||
return aws.NewCredentialsProvider(ctx, awsOpts...)
|
||||
}
|
||||
}
|
||||
|
||||
if d.azureTokenCredential == nil {
|
||||
azureOpts := opts
|
||||
if d.tokenCache != nil {
|
||||
involvedObject.Operation = intcache.OperationDecryptWithAzure
|
||||
azureOpts = append(azureOpts, auth.WithCache(*d.tokenCache, involvedObject))
|
||||
}
|
||||
d.azureTokenCredential = azure.NewTokenCredential(ctx, azureOpts...)
|
||||
}
|
||||
|
||||
if d.gcpTokenSource == nil {
|
||||
gcpOpts := opts
|
||||
if d.tokenCache != nil {
|
||||
involvedObject.Operation = intcache.OperationDecryptWithGCP
|
||||
gcpOpts = append(gcpOpts, auth.WithCache(*d.tokenCache, involvedObject))
|
||||
}
|
||||
d.gcpTokenSource = gcp.NewTokenSource(ctx, gcpOpts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SopsDecryptWithFormat attempts to load a SOPS encrypted file using the store
|
||||
// for the input format, gathers the data key for it from the key service,
|
||||
// and then decrypts the file data with the retrieved data key.
|
||||
|
|
@ -582,12 +656,10 @@ func (d *Decryptor) loadKeyServiceServer() {
|
|||
intkeyservice.WithGnuPGHome(d.gnuPGHome),
|
||||
intkeyservice.WithVaultToken(d.vaultToken),
|
||||
intkeyservice.WithAgeIdentities(d.ageIdentities),
|
||||
intkeyservice.WithGCPCredsJSON(d.gcpCredsJSON),
|
||||
intkeyservice.WithAWSCredentialsProvider{CredentialsProvider: d.awsCredentialsProvider},
|
||||
intkeyservice.WithAzureTokenCredential{TokenCredential: d.azureTokenCredential},
|
||||
intkeyservice.WithGCPTokenSource{TokenSource: d.gcpTokenSource},
|
||||
}
|
||||
if d.azureToken != nil {
|
||||
serverOpts = append(serverOpts, intkeyservice.WithAzureToken{Token: d.azureToken})
|
||||
}
|
||||
serverOpts = append(serverOpts, intkeyservice.WithAWSKeys{CredsProvider: d.awsCredsProvider})
|
||||
server := intkeyservice.NewServer(serverOpts...)
|
||||
d.keyServices = append(make([]keyservice.KeyServiceClient, 0), keyservice.NewCustomLocalClient(server))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ aws_session_token: test-token`),
|
|||
},
|
||||
},
|
||||
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
|
||||
g.Expect(decryptor.awsCredsProvider).ToNot(BeNil())
|
||||
g.Expect(decryptor.awsCredentialsProvider).ToNot(BeNil())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -233,7 +233,7 @@ aws_session_token: test-token`),
|
|||
},
|
||||
},
|
||||
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
|
||||
g.Expect(decryptor.gcpCredsJSON).ToNot(BeNil())
|
||||
g.Expect(decryptor.gcpTokenSource).ToNot(BeNil())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -256,7 +256,7 @@ clientSecret: some-client-secret`),
|
|||
},
|
||||
},
|
||||
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
|
||||
g.Expect(decryptor.azureToken).ToNot(BeNil())
|
||||
g.Expect(decryptor.azureTokenCredential).ToNot(BeNil())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -278,7 +278,7 @@ clientSecret: some-client-secret`),
|
|||
},
|
||||
wantErr: true,
|
||||
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
|
||||
g.Expect(decryptor.azureToken).To(BeNil())
|
||||
g.Expect(decryptor.azureTokenCredential).To(BeNil())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -300,7 +300,7 @@ clientSecret: some-client-secret`),
|
|||
},
|
||||
wantErr: true,
|
||||
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
|
||||
g.Expect(decryptor.azureToken).To(BeNil())
|
||||
g.Expect(decryptor.azureTokenCredential).To(BeNil())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -376,7 +376,7 @@ clientSecret: some-client-secret`),
|
|||
},
|
||||
}
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", cb.Build(), &kustomization)
|
||||
d, cleanup, err := NewTempDecryptor("", cb.Build(), &kustomization, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -393,6 +393,60 @@ clientSecret: some-client-secret`),
|
|||
}
|
||||
}
|
||||
|
||||
func TestDecryptor_SetAuthOptions(t *testing.T) {
|
||||
t.Run("nil decryption settings", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
d := &Decryptor{
|
||||
kustomization: &kustomizev1.Kustomization{},
|
||||
}
|
||||
|
||||
d.SetAuthOptions(context.Background())
|
||||
|
||||
g.Expect(d.awsCredentialsProvider).To(BeNil())
|
||||
g.Expect(d.azureTokenCredential).To(BeNil())
|
||||
g.Expect(d.gcpTokenSource).To(BeNil())
|
||||
})
|
||||
|
||||
t.Run("non-sops provider", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
d := &Decryptor{
|
||||
kustomization: &kustomizev1.Kustomization{
|
||||
Spec: kustomizev1.KustomizationSpec{
|
||||
Decryption: &kustomizev1.Decryption{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d.SetAuthOptions(context.Background())
|
||||
|
||||
g.Expect(d.awsCredentialsProvider).To(BeNil())
|
||||
g.Expect(d.azureTokenCredential).To(BeNil())
|
||||
g.Expect(d.gcpTokenSource).To(BeNil())
|
||||
})
|
||||
|
||||
t.Run("sops provider", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
d := &Decryptor{
|
||||
kustomization: &kustomizev1.Kustomization{
|
||||
Spec: kustomizev1.KustomizationSpec{
|
||||
Decryption: &kustomizev1.Decryption{
|
||||
Provider: DecryptionProviderSOPS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d.SetAuthOptions(context.Background())
|
||||
|
||||
g.Expect(d.awsCredentialsProvider).NotTo(BeNil())
|
||||
g.Expect(d.azureTokenCredential).NotTo(BeNil())
|
||||
g.Expect(d.gcpTokenSource).NotTo(BeNil())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecryptor_SopsDecryptWithFormat(t *testing.T) {
|
||||
t.Run("decrypt INI to INI", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
|
@ -551,7 +605,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
Provider: DecryptionProviderSOPS,
|
||||
}
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -592,7 +646,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
Provider: DecryptionProviderSOPS,
|
||||
}
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -627,7 +681,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
Provider: DecryptionProviderSOPS,
|
||||
}
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -662,7 +716,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
Provider: DecryptionProviderSOPS,
|
||||
}
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -711,7 +765,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
t.Run("nil resource", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy())
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy(), nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -723,7 +777,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
t.Run("no decryption spec", func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy())
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy(), nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
@ -739,7 +793,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
|
|||
kus.Spec.Decryption = &kustomizev1.Decryption{
|
||||
Provider: "not-supported",
|
||||
}
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
|
||||
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package awskms
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetRegionFromKMSARN extracts the region from a KMS ARN.
|
||||
func GetRegionFromKMSARN(arn string) string {
|
||||
arn = strings.TrimPrefix(arn, "arn:aws:kms:")
|
||||
return strings.SplitN(arn, ":", 2)[0]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package awskms_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/awskms"
|
||||
)
|
||||
|
||||
func TestGetRegionFromKMSARN(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
arn := "arn:aws:kms:us-east-1:211125720409:key/mrk-3179bb7e88bc42ffb1a27d5038ceea25"
|
||||
|
||||
region := awskms.GetRegionFromKMSARN(arn)
|
||||
g.Expect(region).To(Equal("us-east-1"))
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package azkv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
)
|
||||
|
||||
// DefaultTokenCredential is a modification of azidentity.NewDefaultAzureCredential,
|
||||
// specifically adapted to not shell out to the Azure CLI.
|
||||
//
|
||||
// It attempts to return an azcore.TokenCredential based on the following order:
|
||||
//
|
||||
// - azidentity.NewEnvironmentCredential if environment variables AZURE_CLIENT_ID,
|
||||
// AZURE_CLIENT_ID is set with either one of the following: (AZURE_CLIENT_SECRET)
|
||||
// or (AZURE_CLIENT_CERTIFICATE_PATH and AZURE_CLIENT_CERTIFICATE_PATH) or
|
||||
// (AZURE_USERNAME, AZURE_PASSWORD)
|
||||
// - azidentity.WorkloadIdentityCredential if environment variable configuration
|
||||
// (AZURE_AUTHORITY_HOST, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_TENANT_ID)
|
||||
// is set by the Azure workload identity webhook.
|
||||
// - azidentity.ManagedIdentityCredential if only AZURE_CLIENT_ID env variable is set.
|
||||
func DefaultTokenCredential() (azcore.TokenCredential, error) {
|
||||
var (
|
||||
azureClientID = "AZURE_CLIENT_ID"
|
||||
azureFederatedTokenFile = "AZURE_FEDERATED_TOKEN_FILE"
|
||||
azureAuthorityHost = "AZURE_AUTHORITY_HOST"
|
||||
azureTenantID = "AZURE_TENANT_ID"
|
||||
)
|
||||
|
||||
var errorMessages []string
|
||||
options := &azidentity.DefaultAzureCredentialOptions{}
|
||||
|
||||
envCred, err := azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{
|
||||
ClientOptions: options.ClientOptions, DisableInstanceDiscovery: options.DisableInstanceDiscovery},
|
||||
)
|
||||
if err == nil {
|
||||
return envCred, nil
|
||||
} else {
|
||||
errorMessages = append(errorMessages, "EnvironmentCredential: "+err.Error())
|
||||
}
|
||||
|
||||
// workload identity requires values for AZURE_AUTHORITY_HOST, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_TENANT_ID
|
||||
haveWorkloadConfig := false
|
||||
clientID, haveClientID := os.LookupEnv(azureClientID)
|
||||
if haveClientID {
|
||||
if file, ok := os.LookupEnv(azureFederatedTokenFile); ok {
|
||||
if _, ok := os.LookupEnv(azureAuthorityHost); ok {
|
||||
if tenantID, ok := os.LookupEnv(azureTenantID); ok {
|
||||
haveWorkloadConfig = true
|
||||
workloadCred, err := azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
TokenFilePath: file,
|
||||
ClientOptions: options.ClientOptions,
|
||||
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
|
||||
})
|
||||
if err == nil {
|
||||
return workloadCred, nil
|
||||
} else {
|
||||
errorMessages = append(errorMessages, "Workload Identity"+": "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !haveWorkloadConfig {
|
||||
err := errors.New("missing environment variables for workload identity. Check webhook and pod configuration")
|
||||
errorMessages = append(errorMessages, fmt.Sprintf("Workload Identity: %s", err))
|
||||
}
|
||||
|
||||
o := &azidentity.ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions}
|
||||
if haveClientID {
|
||||
o.ID = azidentity.ClientID(clientID)
|
||||
}
|
||||
miCred, err := azidentity.NewManagedIdentityCredential(o)
|
||||
if err == nil {
|
||||
return miCred, nil
|
||||
} else {
|
||||
errorMessages = append(errorMessages, "ManagedIdentity"+": "+err.Error())
|
||||
}
|
||||
|
||||
return nil, errors.New(strings.Join(errorMessages, "\n"))
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@ package keyservice
|
|||
|
||||
import (
|
||||
extage "filippo.io/age"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
awssdk "github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/getsops/sops/v3/age"
|
||||
"github.com/getsops/sops/v3/azkv"
|
||||
"github.com/getsops/sops/v3/gcpkms"
|
||||
|
|
@ -25,6 +27,9 @@ import (
|
|||
"github.com/getsops/sops/v3/keyservice"
|
||||
awskms "github.com/getsops/sops/v3/kms"
|
||||
"github.com/getsops/sops/v3/pgp"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
intawskms "github.com/fluxcd/kustomize-controller/internal/sops/awskms"
|
||||
)
|
||||
|
||||
// ServerOption is some configuration that modifies the Server.
|
||||
|
|
@ -57,33 +62,38 @@ func (o WithAgeIdentities) ApplyToServer(s *Server) {
|
|||
s.ageIdentities = age.ParsedIdentities(o)
|
||||
}
|
||||
|
||||
// WithAWSKeys configures the AWS credentials on the Server
|
||||
type WithAWSKeys struct {
|
||||
CredsProvider *awskms.CredentialsProvider
|
||||
// WithAWSCredentialsProvider configures the AWS credentials on the Server
|
||||
type WithAWSCredentialsProvider struct {
|
||||
CredentialsProvider func(region string) awssdk.CredentialsProvider
|
||||
}
|
||||
|
||||
// ApplyToServer applies this configuration to the given Server.
|
||||
func (o WithAWSKeys) ApplyToServer(s *Server) {
|
||||
s.awsCredsProvider = o.CredsProvider
|
||||
func (o WithAWSCredentialsProvider) ApplyToServer(s *Server) {
|
||||
s.awsCredentialsProvider = func(arn string) *awskms.CredentialsProvider {
|
||||
region := intawskms.GetRegionFromKMSARN(arn)
|
||||
cp := o.CredentialsProvider(region)
|
||||
return awskms.NewCredentialsProvider(cp)
|
||||
}
|
||||
}
|
||||
|
||||
// WithGCPCredsJSON configures the GCP service account credentials JSON on the
|
||||
// Server.
|
||||
type WithGCPCredsJSON []byte
|
||||
|
||||
// ApplyToServer applies this configuration to the given Server.
|
||||
func (o WithGCPCredsJSON) ApplyToServer(s *Server) {
|
||||
s.gcpCredsJSON = gcpkms.CredentialJSON(o)
|
||||
}
|
||||
|
||||
// WithAzureToken configures the Azure credential token on the Server.
|
||||
type WithAzureToken struct {
|
||||
Token *azkv.TokenCredential
|
||||
// WithGCPTokenSource configures the GCP token source on the Server.
|
||||
type WithGCPTokenSource struct {
|
||||
TokenSource oauth2.TokenSource
|
||||
}
|
||||
|
||||
// ApplyToServer applies this configuration to the given Server.
|
||||
func (o WithAzureToken) ApplyToServer(s *Server) {
|
||||
s.azureToken = o.Token
|
||||
func (o WithGCPTokenSource) ApplyToServer(s *Server) {
|
||||
s.gcpTokenSource = gcpkms.NewTokenSource(o.TokenSource)
|
||||
}
|
||||
|
||||
// WithAzureTokenCredential configures the Azure credential token on the Server.
|
||||
type WithAzureTokenCredential struct {
|
||||
TokenCredential azcore.TokenCredential
|
||||
}
|
||||
|
||||
// ApplyToServer applies this configuration to the given Server.
|
||||
func (o WithAzureTokenCredential) ApplyToServer(s *Server) {
|
||||
s.azureTokenCredential = azkv.NewTokenCredential(o.TokenCredential)
|
||||
}
|
||||
|
||||
// WithDefaultServer configures the fallback default server on the Server.
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ import (
|
|||
"github.com/getsops/sops/v3/logging"
|
||||
"github.com/getsops/sops/v3/pgp"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
intazkv "github.com/fluxcd/kustomize-controller/internal/sops/azkv"
|
||||
)
|
||||
|
||||
// Server is a key service server that uses SOPS MasterKeys to fulfill
|
||||
|
|
@ -54,20 +52,19 @@ type Server struct {
|
|||
// When empty, the request will be handled by defaultServer.
|
||||
vaultToken hcvault.Token
|
||||
|
||||
// azureToken is the credential token used for Encrypt and Decrypt
|
||||
// azureTokenCredential is the credential token used for Encrypt and Decrypt
|
||||
// operations of Azure Key Vault requests.
|
||||
// When nil, the request will be handled by defaultServer.
|
||||
azureToken *azkv.TokenCredential
|
||||
azureTokenCredential *azkv.TokenCredential
|
||||
|
||||
// awsCredsProvider is the Credentials object used for Encrypt and Decrypt
|
||||
// awsCredentialsProvider is the Credentials object used for Encrypt and Decrypt
|
||||
// operations of AWS KMS requests.
|
||||
// When nil, the request will be handled by defaultServer.
|
||||
awsCredsProvider *awskms.CredentialsProvider
|
||||
awsCredentialsProvider func(arn string) *awskms.CredentialsProvider
|
||||
|
||||
// gcpCredsJSON is the JSON credentials used for Decrypt and Encrypt
|
||||
// operations of GCP KMS requests. When nil, a default client with
|
||||
// environmental runtime settings will be used.
|
||||
gcpCredsJSON gcpkms.CredentialJSON
|
||||
// gcpTokenSource is the token source used for Encrypt and Decrypt
|
||||
// operations of GCP KMS requests.
|
||||
gcpTokenSource gcpkms.TokenSource
|
||||
|
||||
// defaultServer is the fallback server, used to handle any request that
|
||||
// is not eligible to be handled by this Server.
|
||||
|
|
@ -296,9 +293,7 @@ func (ks *Server) decryptWithHCVault(key *keyservice.VaultKey, ciphertext []byte
|
|||
|
||||
func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([]byte, error) {
|
||||
awsKey := kmsKeyToMasterKey(key)
|
||||
if ks.awsCredsProvider != nil {
|
||||
ks.awsCredsProvider.ApplyToMasterKey(&awsKey)
|
||||
}
|
||||
ks.awsCredentialsProvider(key.Arn).ApplyToMasterKey(&awsKey)
|
||||
if err := awsKey.Encrypt(plaintext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -308,9 +303,7 @@ func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([
|
|||
func (ks *Server) decryptWithAWSKMS(key *keyservice.KmsKey, cipherText []byte) ([]byte, error) {
|
||||
awsKey := kmsKeyToMasterKey(key)
|
||||
awsKey.EncryptedKey = string(cipherText)
|
||||
if ks.awsCredsProvider != nil {
|
||||
ks.awsCredsProvider.ApplyToMasterKey(&awsKey)
|
||||
}
|
||||
ks.awsCredentialsProvider(key.Arn).ApplyToMasterKey(&awsKey)
|
||||
return awsKey.Decrypt()
|
||||
}
|
||||
|
||||
|
|
@ -320,17 +313,7 @@ func (ks *Server) encryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, pla
|
|||
Name: key.Name,
|
||||
Version: key.Version,
|
||||
}
|
||||
if ks.azureToken == nil {
|
||||
// Ensure we use the default token credential if none is provided
|
||||
// _without_ shelling out to `az`.
|
||||
defaultToken, err := intazkv.DefaultTokenCredential()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Azure token credential to encrypt data: %w", err)
|
||||
}
|
||||
azkv.NewTokenCredential(defaultToken).ApplyToMasterKey(&azureKey)
|
||||
} else {
|
||||
ks.azureToken.ApplyToMasterKey(&azureKey)
|
||||
}
|
||||
ks.azureTokenCredential.ApplyToMasterKey(&azureKey)
|
||||
if err := azureKey.Encrypt(plaintext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -343,17 +326,7 @@ func (ks *Server) decryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, cip
|
|||
Name: key.Name,
|
||||
Version: key.Version,
|
||||
}
|
||||
if ks.azureToken == nil {
|
||||
// Ensure we use the default token credential if none is provided
|
||||
// _without_ shelling out to `az`.
|
||||
defaultToken, err := intazkv.DefaultTokenCredential()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Azure token credential to decrypt data: %w", err)
|
||||
}
|
||||
azkv.NewTokenCredential(defaultToken).ApplyToMasterKey(&azureKey)
|
||||
} else {
|
||||
ks.azureToken.ApplyToMasterKey(&azureKey)
|
||||
}
|
||||
ks.azureTokenCredential.ApplyToMasterKey(&azureKey)
|
||||
azureKey.EncryptedKey = string(ciphertext)
|
||||
plaintext, err := azureKey.Decrypt()
|
||||
return plaintext, err
|
||||
|
|
@ -363,7 +336,7 @@ func (ks *Server) encryptWithGCPKMS(key *keyservice.GcpKmsKey, plaintext []byte)
|
|||
gcpKey := gcpkms.MasterKey{
|
||||
ResourceID: key.ResourceId,
|
||||
}
|
||||
ks.gcpCredsJSON.ApplyToMasterKey(&gcpKey)
|
||||
ks.gcpTokenSource.ApplyToMasterKey(&gcpKey)
|
||||
if err := gcpKey.Encrypt(plaintext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -374,7 +347,7 @@ func (ks *Server) decryptWithGCPKMS(key *keyservice.GcpKmsKey, ciphertext []byte
|
|||
gcpKey := gcpkms.MasterKey{
|
||||
ResourceID: key.ResourceId,
|
||||
}
|
||||
ks.gcpCredsJSON.ApplyToMasterKey(&gcpKey)
|
||||
ks.gcpTokenSource.ApplyToMasterKey(&gcpKey)
|
||||
gcpKey.EncryptedKey = string(ciphertext)
|
||||
plaintext, err := gcpKey.Decrypt()
|
||||
return plaintext, err
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
gcpkmsapi "cloud.google.com/go/kms/apiv1"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/getsops/sops/v3/age"
|
||||
"github.com/getsops/sops/v3/azkv"
|
||||
|
|
@ -32,6 +34,7 @@ import (
|
|||
"github.com/getsops/sops/v3/pgp"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
func TestServer_EncryptDecrypt_PGP(t *testing.T) {
|
||||
|
|
@ -151,8 +154,8 @@ func TestServer_EncryptDecrypt_HCVault_Fallback(t *testing.T) {
|
|||
|
||||
func TestServer_EncryptDecrypt_awskms(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
s := NewServer(WithAWSKeys{
|
||||
CredsProvider: awskms.NewCredentialsProvider(credentials.StaticCredentialsProvider{}),
|
||||
s := NewServer(WithAWSCredentialsProvider{
|
||||
CredentialsProvider: func(region string) aws.CredentialsProvider { return credentials.StaticCredentialsProvider{} },
|
||||
})
|
||||
|
||||
key := KeyFromMasterKey(awskms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, ""))
|
||||
|
|
@ -174,7 +177,7 @@ func TestServer_EncryptDecrypt_azkv(t *testing.T) {
|
|||
|
||||
identity, err := azidentity.NewDefaultAzureCredential(nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
s := NewServer(WithAzureToken{Token: azkv.NewTokenCredential(identity)})
|
||||
s := NewServer(WithAzureTokenCredential{TokenCredential: identity})
|
||||
|
||||
key := KeyFromMasterKey(azkv.NewMasterKey("", "", ""))
|
||||
_, err = s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
|
|
@ -194,24 +197,24 @@ func TestServer_EncryptDecrypt_azkv(t *testing.T) {
|
|||
func TestServer_EncryptDecrypt_gcpkms(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
creds := `{ "client_id": "<client-id>.apps.googleusercontent.com",
|
||||
"client_secret": "<secret>",
|
||||
"type": "authorized_user"}`
|
||||
s := NewServer(WithGCPCredsJSON([]byte(creds)))
|
||||
creds, err := google.CredentialsFromJSON(context.Background(),
|
||||
[]byte(`{"type":"service_account"}`), gcpkmsapi.DefaultAuthScopes()...)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
s := NewServer(WithGCPTokenSource{TokenSource: creds.TokenSource})
|
||||
|
||||
resourceID := "projects/test-flux/locations/global/keyRings/test-flux/cryptoKeys/sops"
|
||||
key := KeyFromMasterKey(gcpkms.NewMasterKeyFromResourceID(resourceID))
|
||||
_, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
_, err = s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("cannot create GCP KMS service"))
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with GCP KMS key"))
|
||||
|
||||
_, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("cannot create GCP KMS service"))
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with GCP KMS key"))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
22
main.go
22
main.go
|
|
@ -32,10 +32,12 @@ import (
|
|||
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/config"
|
||||
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
|
||||
pkgcache "github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
runtimeClient "github.com/fluxcd/pkg/runtime/client"
|
||||
runtimeCtrl "github.com/fluxcd/pkg/runtime/controller"
|
||||
|
|
@ -73,6 +75,10 @@ func init() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
const (
|
||||
tokenCacheDefaultMaxSize = 100
|
||||
)
|
||||
|
||||
var (
|
||||
metricsAddr string
|
||||
eventsAddr string
|
||||
|
|
@ -93,6 +99,7 @@ func main() {
|
|||
defaultServiceAccount string
|
||||
featureGates feathelper.FeatureGates
|
||||
disallowedFieldManagers []string
|
||||
tokenCacheOptions pkgcache.TokenFlags
|
||||
)
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
|
|
@ -116,6 +123,7 @@ func main() {
|
|||
featureGates.BindFlags(flag.CommandLine)
|
||||
watchOptions.BindFlags(flag.CommandLine)
|
||||
intervalJitterOptions.BindFlags(flag.CommandLine)
|
||||
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
|
@ -240,6 +248,19 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
var tokenCache *pkgcache.TokenCache
|
||||
if tokenCacheOptions.MaxSize > 0 {
|
||||
var err error
|
||||
tokenCache, err = pkgcache.NewTokenCache(tokenCacheOptions.MaxSize,
|
||||
pkgcache.WithMaxDuration(tokenCacheOptions.MaxDuration),
|
||||
pkgcache.WithMetricsRegisterer(ctrlmetrics.Registry),
|
||||
pkgcache.WithMetricsPrefix("gotk_token_"))
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create token cache")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err = (&controller.KustomizationReconciler{
|
||||
ControllerName: controllerName,
|
||||
DefaultServiceAccount: defaultServiceAccount,
|
||||
|
|
@ -257,6 +278,7 @@ func main() {
|
|||
DisallowedFieldManagers: disallowedFieldManagers,
|
||||
StrictSubstitutions: strictSubstitutions,
|
||||
GroupChangeLog: groupChangeLog,
|
||||
TokenCache: tokenCache,
|
||||
}).SetupWithManager(ctx, mgr, controller.KustomizationReconcilerOptions{
|
||||
DependencyRequeueInterval: requeueDependency,
|
||||
HTTPRetry: httpRetry,
|
||||
|
|
|
|||
Loading…
Reference in New Issue