Compare commits
5 Commits
rfc-passwo
...
context-ns
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01e6a6c3b | ||
|
|
12efb1967e | ||
|
|
56b1e80758 | ||
|
|
baf874ea67 | ||
|
|
28262f59d3 |
@@ -57,7 +57,7 @@ type checkFlags struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var kubernetesConstraints = []string{
|
var kubernetesConstraints = []string{
|
||||||
">=1.24.0-0",
|
">=1.25.0-0",
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkArgs checkFlags
|
var checkArgs checkFlags
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
|||||||
var repo = imagev1.ImageRepository{
|
var repo = imagev1.ImageRepository{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: objectName,
|
Name: objectName,
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
Namespace: GetDesiredNamespace(kubeconfigArgs),
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
},
|
},
|
||||||
Spec: imagev1.ImageRepositorySpec{
|
Spec: imagev1.ImageRepositorySpec{
|
||||||
|
|||||||
@@ -91,6 +91,12 @@ func TestDiffKustomization(t *testing.T) {
|
|||||||
objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml",
|
objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml",
|
||||||
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden"),
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "diff where kustomization file has multiple objects with the same name",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml",
|
||||||
|
objectFile: "",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := map[string]string{
|
tmpl := map[string]string{
|
||||||
|
|||||||
@@ -146,9 +146,11 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ns := GetDesiredNamespace(kubeconfigArgs)
|
||||||
|
|
||||||
var listOpts []client.ListOption
|
var listOpts []client.ListOption
|
||||||
if !getArgs.allNamespaces {
|
if !getArgs.allNamespaces {
|
||||||
listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace))
|
listOpts = append(listOpts, client.InNamespace(ns))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@@ -190,12 +192,12 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Failuref("%s object '%s' not found in %s namespace",
|
logger.Failuref("%s object '%s' not found in %s namespace",
|
||||||
get.kind,
|
get.kind,
|
||||||
args[0],
|
args[0],
|
||||||
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
|
namespaceNameOrAny(getArgs.allNamespaces, ns),
|
||||||
)
|
)
|
||||||
} else if !getAll {
|
} else if !getAll {
|
||||||
logger.Failuref("no %s objects found in %s namespace",
|
logger.Failuref("no %s objects found in %s namespace",
|
||||||
get.kind,
|
get.kind,
|
||||||
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
|
namespaceNameOrAny(getArgs.allNamespaces, ns),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
opts := install.Options{
|
opts := install.Options{
|
||||||
BaseURL: installArgs.manifestsPath,
|
BaseURL: installArgs.manifestsPath,
|
||||||
Version: installArgs.version,
|
Version: installArgs.version,
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
Namespace: GetDesiredNamespace(kubeconfigArgs),
|
||||||
Components: components,
|
Components: components,
|
||||||
Registry: installArgs.registry,
|
Registry: installArgs.registry,
|
||||||
ImagePullSecret: installArgs.imagePullSecret,
|
ImagePullSecret: installArgs.imagePullSecret,
|
||||||
@@ -181,7 +181,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Successf("manifests build completed")
|
logger.Successf("manifests build completed")
|
||||||
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
|
logger.Actionf("installing components in %s namespace", opts.Namespace)
|
||||||
|
|
||||||
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
|
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
|
|||||||
return fmt.Errorf("error getting namespace: %w", err)
|
return fmt.Errorf("error getting namespace: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if e := validation.IsDNS1123Label(ns); len(e) > 0 {
|
if e := validation.IsDNS1123Label(ns); len(e) > 0 {
|
||||||
return fmt.Errorf("namespace must be a valid DNS label: %q", ns)
|
return fmt.Errorf("namespace must be a valid DNS label: %q", ns)
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,6 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
|
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
|
||||||
rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
|
rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
|
||||||
|
|
||||||
configureDefaultNamespace()
|
|
||||||
kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag
|
kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag
|
||||||
kubeconfigArgs.Timeout = nil // prevent AddFlags from configuring --request-timeout flag, we have --timeout instead
|
kubeconfigArgs.Timeout = nil // prevent AddFlags from configuring --request-timeout flag, we have --timeout instead
|
||||||
kubeconfigArgs.AddFlags(rootCmd.PersistentFlags())
|
kubeconfigArgs.AddFlags(rootCmd.PersistentFlags())
|
||||||
@@ -198,8 +201,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureDefaultNamespace() {
|
func GetDesiredNamespace(cfg *genericclioptions.ConfigFlags) string {
|
||||||
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
|
if *cfg.Namespace != "" {
|
||||||
|
return *cfg.Namespace
|
||||||
|
}
|
||||||
fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE")
|
fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE")
|
||||||
if fromEnv != "" {
|
if fromEnv != "" {
|
||||||
// namespace must be a valid DNS label. Assess against validation
|
// namespace must be a valid DNS label. Assess against validation
|
||||||
@@ -207,11 +212,28 @@ func configureDefaultNamespace() {
|
|||||||
// may not be actively provided by end-user.
|
// may not be actively provided by end-user.
|
||||||
if e := validation.IsDNS1123Label(fromEnv); len(e) > 0 {
|
if e := validation.IsDNS1123Label(fromEnv); len(e) > 0 {
|
||||||
logger.Warningf(" ignoring invalid FLUX_SYSTEM_NAMESPACE: %q", fromEnv)
|
logger.Warningf(" ignoring invalid FLUX_SYSTEM_NAMESPACE: %q", fromEnv)
|
||||||
return
|
} else {
|
||||||
|
return fromEnv
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeconfigArgs.Namespace = &fromEnv
|
if _, has := os.LookupEnv("FLUX_NS_FOLLOW_KUBECONTEXT"); has {
|
||||||
|
rawCfg, err := cfg.ToRawKubeConfigLoader().RawConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf(" failed parsing kubeconfig, ignoring: %q", fromEnv)
|
||||||
|
} else {
|
||||||
|
ctx := *cfg.Context
|
||||||
|
if ctx == "" {
|
||||||
|
ctx = rawCfg.CurrentContext
|
||||||
}
|
}
|
||||||
|
ns := rawCfg.Contexts[ctx].Namespace
|
||||||
|
if ns != "" {
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootArgs.defaults.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPasswordFromStdin reads a password from stdin and returns the input
|
// readPasswordFromStdin reads a password from stdin and returns the input
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error
|
|||||||
var objRefs []object.ObjMetadata
|
var objRefs []object.ObjMetadata
|
||||||
for _, deployment := range components {
|
for _, deployment := range components {
|
||||||
objRefs = append(objRefs, object.ObjMetadata{
|
objRefs = append(objRefs, object.ObjMetadata{
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
Namespace: GetDesiredNamespace(kubeconfigArgs),
|
||||||
Name: deployment,
|
Name: deployment,
|
||||||
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
|
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
|
||||||
})
|
})
|
||||||
|
|||||||
2
cmd/flux/testdata/check/check_pre.golden
vendored
2
cmd/flux/testdata/check/check_pre.golden
vendored
@@ -1,3 +1,3 @@
|
|||||||
► checking prerequisites
|
► checking prerequisites
|
||||||
✔ Kubernetes {{ .serverVersion }} >=1.24.0-0
|
✔ Kubernetes {{ .serverVersion }} >=1.25.0-0
|
||||||
✔ prerequisites checks passed
|
✔ prerequisites checks passed
|
||||||
|
|||||||
19
cmd/flux/testdata/diff-kustomization/flux-kustomization-multiobj.yaml
vendored
Normal file
19
cmd/flux/testdata/diff-kustomization/flux-kustomization-multiobj.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
|
spec:
|
||||||
|
interval: 5m0s
|
||||||
|
path: ./kustomize
|
||||||
|
force: true
|
||||||
|
prune: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: podinfo
|
||||||
|
targetNamespace: default
|
||||||
@@ -333,7 +333,7 @@ func (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error)
|
|||||||
k := &kustomizev1.Kustomization{}
|
k := &kustomizev1.Kustomization{}
|
||||||
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(data), len(data))
|
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(data), len(data))
|
||||||
// check for kustomization in yaml with the same name and namespace
|
// check for kustomization in yaml with the same name and namespace
|
||||||
for !(k.Name == b.name && (k.Namespace == b.namespace || k.Namespace == "")) {
|
for {
|
||||||
err = decoder.Decode(k)
|
err = decoder.Decode(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -343,6 +343,13 @@ func (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error)
|
|||||||
return nil, fmt.Errorf("failed to unmarshall kustomization file %s: %w", b.kustomizationFile, err)
|
return nil, fmt.Errorf("failed to unmarshall kustomization file %s: %w", b.kustomizationFile, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(k.APIVersion, kustomizev1.GroupVersion.Group+"/") &&
|
||||||
|
k.Kind == kustomizev1.KustomizationKind &&
|
||||||
|
k.Name == b.name &&
|
||||||
|
(k.Namespace == b.namespace || k.Namespace == "") {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return k, nil
|
return k, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,6 +189,12 @@ func Test_unMarshallKustomization(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
errString: "failed find kustomization with name",
|
errString: "failed find kustomization with name",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "yaml containing other resource with same name as kustomization",
|
||||||
|
localKsFile: "testdata/local-kustomization/invalid-resource.yaml",
|
||||||
|
wantErr: true,
|
||||||
|
errString: "failed find kustomization with name",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b := &Builder{
|
b := &Builder{
|
||||||
@@ -324,7 +330,10 @@ func Test_ResolveKustomization(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b := &Builder{}
|
b := &Builder{
|
||||||
|
name: "podinfo",
|
||||||
|
namespace: "flux-system",
|
||||||
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
b.kustomizationFile = tt.localKsFile
|
b.kustomizationFile = tt.localKsFile
|
||||||
|
|||||||
4
internal/build/testdata/local-kustomization/invalid-resource.yaml
vendored
Normal file
4
internal/build/testdata/local-kustomization/invalid-resource.yaml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
# RFC-0006 Passwordless authentication for Git repositories
|
|
||||||
|
|
||||||
**Status:** provisional
|
|
||||||
|
|
||||||
**Creation date:** 2023-31-07
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Flux should provide a mechanism to authenticate against Git repositories without
|
|
||||||
the use of passwords. This RFC proposes the use of alternative authentication
|
|
||||||
methods like OIDC, OAuth2 and IAM to access Git repositories hosted on specific
|
|
||||||
Git SaaS platforms and cloud providers.
|
|
||||||
|
|
||||||
## Motivation
|
|
||||||
|
|
||||||
At the moment, Flux supports HTTP basic and bearer authentication. Users are
|
|
||||||
required to create a Secret containing the username and the password/bearer
|
|
||||||
token, which is then referred to in the GitRepository using `.spec.secretRef`.
|
|
||||||
|
|
||||||
While this works fine, it has a couple of drawbacks:
|
|
||||||
* Scalability: Each new GitRepository potentially warrants another credentials
|
|
||||||
pair, which doesn't scale well in big organizations with hundreds of
|
|
||||||
repositories with different owners, increasing the risk of mismanagement and
|
|
||||||
leaks.
|
|
||||||
* Identity: A username is associated with an actual human. But often, the
|
|
||||||
repository belongs to a team of 2 or more people. This leads to a problem where
|
|
||||||
teams have to decide whose credentials should Flux use for authentication.
|
|
||||||
|
|
||||||
These problems exist not due to flaws in Flux, but because of the inherent
|
|
||||||
nature of password based authentication.
|
|
||||||
|
|
||||||
With support for OIDC, OAuth2 and IAM based authentication, we can eliminate
|
|
||||||
these problems:
|
|
||||||
* Scalability: Since OIDC is fully handled by the cloud provider, it eliminates
|
|
||||||
any user involvement in managing credentials. For OAuth2 and IAM, users do need
|
|
||||||
to provide certain information like the ID of the resource, private key, etc.
|
|
||||||
but these are still a better alternative to passwords since the same resource
|
|
||||||
can be reused by multiple teams with different members.
|
|
||||||
* Identity: Since all the above authentication methods are associated with a
|
|
||||||
virtual resource independent of a user, it solves the problem of a single person
|
|
||||||
being tied to automation that several people are involved in.
|
|
||||||
|
|
||||||
### Goals
|
|
||||||
|
|
||||||
* Integrate with major cloud providers' OIDC and IAM offerings to provide a
|
|
||||||
seamless way of Git repository authentication.
|
|
||||||
* Integrate with major Git SaaS providers to support their app based OAuth2
|
|
||||||
mechanism.
|
|
||||||
|
|
||||||
### Non-Goals
|
|
||||||
* Replace the existing basic and bearer authentication API.
|
|
||||||
|
|
||||||
## Proposal
|
|
||||||
|
|
||||||
A new string field `.spec.provider` shall be added to the GitRepository API.
|
|
||||||
The field will be an enum with the following variants:
|
|
||||||
* `azure`
|
|
||||||
* `github`
|
|
||||||
* `gcp`
|
|
||||||
|
|
||||||
> AWS CodeCommit is not supported as it does not support authentication via IAM
|
|
||||||
Roles without the use of https://github.com/aws/git-remote-codecommit.
|
|
||||||
|
|
||||||
By default, it will be blank, which indicates that the user wants to
|
|
||||||
authenticate via HTTP basic/bearer auth or SSH.
|
|
||||||
|
|
||||||
### Azure
|
|
||||||
|
|
||||||
Git repositories hosted on Azure Devops can be accessed by Flux using OIDC if
|
|
||||||
the cluster running Flux is hosted on AKS with [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops)
|
|
||||||
enabled. The managed identity associated with the cluster must have sufficient
|
|
||||||
permissions to be able to access Azure Devops resources. This enables Flux to
|
|
||||||
access the Git repository without the need for any credentials.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: source.toolkit.fluxcd.io/v1
|
|
||||||
kind: GitRepository
|
|
||||||
metadata:
|
|
||||||
name: azure-devops
|
|
||||||
spec:
|
|
||||||
interval: 1m
|
|
||||||
url: https://dev.azure.com/<org>/<project>/_git/<repository>
|
|
||||||
ref:
|
|
||||||
branch: master
|
|
||||||
# notice the lack of secretRef
|
|
||||||
provider: azure
|
|
||||||
```
|
|
||||||
|
|
||||||
### GCP
|
|
||||||
|
|
||||||
Git repositories hosted on Google Cloud Source Repositories can be accessed by
|
|
||||||
Flux via a [GCP Service Account](https://cloud.google.com/iam/docs/service-account-overview).
|
|
||||||
The Service Account must have sufficient permissions to be able to access Google
|
|
||||||
Cloud Source Repositories and its credentials should be specified in the secret
|
|
||||||
referred to in `.spec.secretRef`.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: source.toolkit.fluxcd.io/v1
|
|
||||||
kind: GitRepository
|
|
||||||
metadata:
|
|
||||||
name: gcp-repo
|
|
||||||
spec:
|
|
||||||
interval: 1m
|
|
||||||
url: https://source.developers.google.com/p/<project>/r/<repository>
|
|
||||||
ref:
|
|
||||||
branch: master
|
|
||||||
provider: gcp
|
|
||||||
secretRef:
|
|
||||||
name: gcp-sa
|
|
||||||
---
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: gcp-sa
|
|
||||||
stringData:
|
|
||||||
gcpServiceAccount: |
|
|
||||||
{
|
|
||||||
"type": "service_account",
|
|
||||||
"project_id": "my-google-project",
|
|
||||||
"private_key_id": "REDACTED",
|
|
||||||
"private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
|
|
||||||
"client_email": "<service-account-id>@my-google-project.iam.gserviceaccount.com",
|
|
||||||
"client_id": "REDACTED",
|
|
||||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
"token_uri": "https://oauth2.googleapis.com/token",
|
|
||||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
|
||||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/<service-account-id>%40my-google-project.iam.gserviceaccount.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GitHub
|
|
||||||
|
|
||||||
Git repositories hosted on GitHub can be accessed via [GitHub Apps](https://docs.github.com/en/apps/overview).
|
|
||||||
This allows users to create a single resource from which they can access all
|
|
||||||
their GitHub repositories. The app must have sufficient permissions to be able
|
|
||||||
to access repositories. The app's ID, private key and installation ID should
|
|
||||||
be mentioned in the Secret referred to by `.spec.secretRef`. GitHub Enterprise
|
|
||||||
users will also need to mention their GitHub API URL in the Secret.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: source.toolkit.fluxcd.io/v1
|
|
||||||
kind: GitRepository
|
|
||||||
metadata:
|
|
||||||
name: github-repo
|
|
||||||
spec:
|
|
||||||
interval: 1m
|
|
||||||
url: https://github.com/<org>/<repository>
|
|
||||||
ref:
|
|
||||||
branch: master
|
|
||||||
provider: github
|
|
||||||
secretRef:
|
|
||||||
name: github-app
|
|
||||||
---
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: gcp-sa
|
|
||||||
stringData:
|
|
||||||
githubAppID: <app-id>
|
|
||||||
githubInstallationID: <installation-id>
|
|
||||||
githubPrivateKey: |
|
|
||||||
<PEM-private-key>
|
|
||||||
githubApiURl: <github-enterprise-api-url> #optional, required only for GitHub Enterprise users
|
|
||||||
```
|
|
||||||
|
|
||||||
## Design Details
|
|
||||||
|
|
||||||
### Azure
|
|
||||||
|
|
||||||
If `.spec.provider` is set to `azure`, Flux controllers will reach out to
|
|
||||||
[Azure IMDS (Instance Metadata Service)](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-go)
|
|
||||||
to get an access token. This [access token will be then used as a bearer token](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#q-can-i-use-a-service-principal-to-do-git-operations-like-clone-a-repo)
|
|
||||||
to perform HTTP bearer authentication.
|
|
||||||
|
|
||||||
### GCP
|
|
||||||
|
|
||||||
If `.spec.provider` is set to `gcp`, Flux controllers will get the Service
|
|
||||||
Account credentials from the specified Secret and use
|
|
||||||
[`google.CredentialsFromJSON`](https://pkg.go.dev/golang.org/x/oauth2/google#CredentialsFromJSON)
|
|
||||||
to fetch the access token. This access token will be then used as the password
|
|
||||||
and the `client_email` as the username to perform HTTP basic authentication.
|
|
||||||
|
|
||||||
### GitHub
|
|
||||||
|
|
||||||
If `.spec.provider` is set to `github`, Flux controllers will get the app
|
|
||||||
details from the specified Secret and use it to [generate an app installation
|
|
||||||
token](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app).
|
|
||||||
This token is then used as the password and [`x-access-token` as the username](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app#choosing-permissions-for-git-access)
|
|
||||||
to perform HTTP basic authentication.
|
|
||||||
|
|
||||||
### Token Caching and Refreshing
|
|
||||||
|
|
||||||
To avoid calling the upstream API for a token during every reconciliation, Flux
|
|
||||||
controllers shall cache the token after fetching it. Since GitHub tokens
|
|
||||||
self-expire, the cache shall automatically evict the token after it has expired,
|
|
||||||
triggering a fetch of a fresh token.
|
|
||||||
For GCP, the [`TokenSource`](https://pkg.go.dev/golang.org/x/oauth2@v0.10.0#TokenSource)
|
|
||||||
object will be cached, since it automatically handles refreshing an expired
|
|
||||||
token and always returns a valid token. Since a `TokenSource` never expires, it
|
|
||||||
need not be evicted from the cache.
|
|
||||||
While Azure's managed identities subsystem caches the token, it is
|
|
||||||
[recommended for the consumer application to implement their own caching](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#token-caching)
|
|
||||||
as well.
|
|
||||||
The caches for all three providers are separate, i.e. there shall exist a
|
|
||||||
dedicated cache for each provider.
|
|
||||||
|
|
||||||
Since the proposed authentication methods for GitHub and GCP involve some form
|
|
||||||
of credentials stored in a Kubernetes Secret, the cache key can be the Secret's
|
|
||||||
`<namespace/name>`. Since authentication for Azure is configured directly via
|
|
||||||
the source-controller Deployment, the token can just be stored in a global
|
|
||||||
variable, which is refreshed whenever the token expires.
|
|
||||||
Reference in New Issue
Block a user