Implement autologin for GCP and Azure

Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>

Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
This commit is contained in:
Somtochi Onyekwere 2021-11-05 11:07:58 +01:00
parent 86d5c2915c
commit fcd6c921b6
7 changed files with 303 additions and 7 deletions

View File

@ -27,6 +27,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
# azure-sdk-for-go requires 1.16 or greater
- uses: actions/setup-go@v2
with:
go-version: '1.17.x'
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:

View File

@ -25,6 +25,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
@ -46,6 +47,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
@ -56,6 +60,7 @@ import (
"github.com/fluxcd/pkg/runtime/predicates"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
"github.com/fluxcd/image-reflector-controller/internal/azure"
)
// These are intended to match the keys used in e.g.,
@ -80,7 +85,9 @@ type ImageRepositoryReconciler struct {
DatabaseReader
}
AwsAutoLogin bool // automatically attempt to get credentials for images in ECR
AwsAutoLogin bool // automatically attempt to get credentials for images in ECR
GcpAutoLogin bool // automatically attempt to get credentials for images in GCP
AzureAutoLogin bool // automatically attempt to get credentials for images in ACR
}
type ImageRepositoryReconcilerOptions struct {
@ -91,6 +98,12 @@ type dockerConfig struct {
Auths map[string]authn.AuthConfig
}
type gceToken struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
}
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagerepositories,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagerepositories/status,verbs=get;update;patch
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
@ -246,6 +259,53 @@ func getAwsECRLoginAuth(accountId, awsEcrRegion string) (authn.AuthConfig, error
return authConfig, nil
}
// getGCRLoginAuth obtains authentication for the image by
// getting a token from the metadata API on GCP. This assumes that
// the pod has right to pull the image which would be the case if it
// is hosted on GCP. It works with both service account and workload identity
// enabled clusters.
func getGCRLoginAuth(ctx context.Context) (authn.AuthConfig, error) {
var authConfig authn.AuthConfig
const gcpDefaultTokenURL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
request, err := http.NewRequestWithContext(ctx, http.MethodGet, gcpDefaultTokenURL, nil)
if err != nil {
return authConfig, err
}
request.Header.Add("Metadata-Flavor", "Google")
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return authConfig, err
}
if response.StatusCode != http.StatusOK {
return authConfig, fmt.Errorf("unexpected status from metadata service: %s", response.Status)
}
var accessToken gceToken
decoder := json.NewDecoder(response.Body)
if err := decoder.Decode(&accessToken); err != nil {
return authConfig, err
}
if _, err := io.Copy(io.Discard, response.Body); err != nil {
return authConfig, err
}
if err := response.Body.Close(); err != nil {
return authConfig, err
}
authConfig = authn.AuthConfig{
Username: "oauth2accesstoken",
Password: accessToken.AccessToken,
}
return authConfig, nil
}
func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1.ImageRepository, ref name.Reference) error {
timeout := imageRepo.GetTimeout()
ctx, cancel := context.WithTimeout(ctx, timeout)
@ -297,6 +357,46 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1
} else {
ctrl.LoggerFrom(ctx).Info("No image credentials secret referenced, and ECR authentication is not enabled. To enable, set the controller flag --aws-autologin-for-ecr")
}
} else if hostIsGoogleContainerRegistry(ref.Context().RegistryStr()) {
if r.GcpAutoLogin {
ctrl.LoggerFrom(ctx).Info("Logging in to GCP GCR for " + imageRepo.Spec.Image)
authConfig, err := getGCRLoginAuth(ctx)
if err != nil {
ctrl.LoggerFrom(ctx).Info("error logging into GCP " + err.Error())
imagev1.SetImageRepositoryReadiness(
imageRepo,
metav1.ConditionFalse,
meta.ReconciliationFailedReason,
err.Error(),
)
return err
}
auth := authn.FromConfig(authConfig)
options = append(options, remote.WithAuth(auth))
} else {
ctrl.LoggerFrom(ctx).Info("No image credentials secret referenced, and GCR authentication is not enabled. To enable, set the controller flag --gcp-autologin-for-gcr")
}
} else if hostIsAzureContainerRegistry(ref.Context().RegistryStr()) {
if r.AzureAutoLogin {
ctrl.LoggerFrom(ctx).Info("Logging in to Azure ACR for " + imageRepo.Spec.Image)
authConfig, err := getAzureLoginAuth(ctx, ref)
if err != nil {
ctrl.LoggerFrom(ctx).Info("error logging into ACR " + err.Error())
imagev1.SetImageRepositoryReadiness(
imageRepo,
metav1.ConditionFalse,
meta.ReconciliationFailedReason,
err.Error(),
)
return err
}
auth := authn.FromConfig(authConfig)
options = append(options, remote.WithAuth(auth))
} else {
ctrl.LoggerFrom(ctx).Info("No image credentials secret referenced, and ACR authentication is not enabled. To enable, set the controller flag --azure-autologin-for-acr")
}
}
if imageRepo.Spec.CertSecretRef != nil {
@ -591,3 +691,47 @@ func getURLHost(urlStr string) (string, error) {
return u.Host, nil
}
// getAzureLoginAuth returns authentication for ACR. The details needed for authentication
// are gotten from environment variable so there is not need to mount a host path.
func getAzureLoginAuth(ctx context.Context, ref name.Reference) (authn.AuthConfig, error) {
var authConfig authn.AuthConfig
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return authConfig, err
}
armToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{string(arm.AzurePublicCloud)},
})
if err != nil {
return authConfig, err
}
ex := azure.NewExchanger(ref.Context().RegistryStr())
accessToken, err := ex.ExchangeACRAccessToken(string(armToken.Token))
if err != nil {
return authConfig, fmt.Errorf("error exchanging token: %w", err)
}
return authn.AuthConfig{
// this is the acr username used by Azure
// See documentation: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli#az-acr-login-with---expose-token
Username: "00000000-0000-0000-0000-000000000000",
Password: accessToken,
}, nil
}
// List from https://github.com/kubernetes/kubernetes/blob/v1.23.1/pkg/credentialprovider/azure/azure_credentials.go#L55
func hostIsAzureContainerRegistry(host string) bool {
for _, v := range []string{".azurecr.io", ".azurecr.cn", ".azurecr.de", ".azurecr.us"} {
if strings.HasSuffix(host, v) {
return true
}
}
return false
}
func hostIsGoogleContainerRegistry(host string) bool {
return host == "gcr.io" || strings.HasSuffix(host, ".gcr.io") || strings.HasSuffix(host, "-docker.pkg.dev")
}

View File

@ -73,13 +73,18 @@ repository. This secret is expected to be in the same format as for
For a publicly accessible image repository, you will not need to provide a `secretRef`.
#### ECR and EKS
#### Automatic Authentication
When running in [<abbr title="Elastic Kubernetes Service">EKS</abbr>][EKS] and using [<abbr
title="Elastic Container Registry">ECR</abbr>][ECR] to store images, you should be able to rely on
the controller retrieving credentials automatically. The controller must be run with the flag
`--aws-autologin-for-ecr` set for this to work. The advice under "Other platforms" below will also
work for ECR.
When running on any of the three major cloud providers and using their container registry to store images,
you should be able to rely on the controller retrieving credentials automatically. The controllers must be run
with the corresponding flag for each provider.
For [<abbr title="Elastic Kubernetes Service">EKS</abbr>][EKS] and [<abbr title="Elastic Container Registry">ECR</abbr>][ECR],
the flag is `--aws-autologin-for-ecr`.
For [<abbr title="Google Kubernetes Engine">GKE</abbr>][GKE] and [<abbr title="Google Container Registry">GCR</abbr>][GCR],
the flag is `--gcp-autologin-for-gcr`.
For [<abbr title="Azure Kubernetes Service">AKS</abbr>][AKS] and [<abbr title="Azure Container Registry">ACR</abbr>][ACR],
the flag is `--azure-autologin-for-acr`.
#### Other platforms
@ -261,3 +266,7 @@ and reference it under `secretRef`.
[sops-guide]: https://toolkit.fluxcd.io/guides/mozilla-sops/
[EKS]: https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html
[ECR]: https://docs.aws.amazon.com/AmazonECR/latest/userguide/what-is-ecr.html
[GKE]: https://cloud.google.com/kubernetes-engine/docs/concepts/kubernetes-engine-overview
[GCR]: https://cloud.google.com/container-registry/docs/overview
[AKS]: https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes
[ACR]: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-intro

5
go.mod
View File

@ -5,6 +5,8 @@ go 1.17
replace github.com/fluxcd/image-reflector-controller/api => ./api
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.12.0
github.com/Masterminds/semver/v3 v3.1.1
github.com/aws/aws-sdk-go v1.42.9
github.com/dgraph-io/badger/v3 v3.2103.2
@ -23,6 +25,7 @@ require (
)
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 // indirect
cloud.google.com/go v0.97.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
@ -61,6 +64,7 @@ require (
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2-0.20210730191737-8e42a01fb1b7 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
@ -72,6 +76,7 @@ require (
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect

18
go.sum
View File

@ -46,7 +46,14 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible h1:KnPIugL51v3N3WwvaSmZbxukD1WuWXOiE9fRdu32f2I=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 h1:KQgdWmEOmaJKxaUUZwHAYh12t+b+ZJf8q3friycK1kA=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0/go.mod h1:ZPW/Z0kLCTdDZaDbYTetxc9Cxl/2lNqxYHYNOF2bti0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.12.0 h1:VBvHGLJbaY0+c66NZHdS9cgjHVYSH6DDa0XJMyrblsI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.12.0/go.mod h1:GJzjM4SR9T0KyX5gKCVyz1ytD8FeWeUPCwtFCt1AyfE=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 h1:BUYIbDf/mMZ8945v3QkG3OuqGVyS4Iek0AOLwdRAYoc=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
@ -289,6 +296,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/docker/cli v20.10.10+incompatible h1:kcbwdgWbrBOH8QwQzaJmyriHwF7XIl4HT1qh0HTRys4=
github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
@ -627,6 +635,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
@ -690,6 +699,9 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -905,8 +917,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -982,6 +997,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -995,6 +1011,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -1120,6 +1137,7 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

110
internal/azure/exchanger.go Normal file
View File

@ -0,0 +1,110 @@
/*
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
*/
/*
Copyright 2022 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.
This package exchanges an ARM access token for an ACR access token on Azure
It has been derived from
https://github.com/Azure/msi-acrpull/blob/main/pkg/authorizer/token_exchanger.go
since the project isn't actively maintained.
*/
package azure
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type tokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Resource string `json:"resource"`
TokenType string `json:"token_type"`
}
type acrError struct {
Code string `json:"code"`
Message string `json:"message"`
}
type Exchanger struct {
acrFQDN string
}
func NewExchanger(acrEndpoint string) *Exchanger {
return &Exchanger{
acrFQDN: acrEndpoint,
}
}
func (e *Exchanger) ExchangeACRAccessToken(armToken string) (string, error) {
exchangeUrl := fmt.Sprintf("https://%s/oauth2/exchange", e.acrFQDN)
parsedURL, err := url.Parse(exchangeUrl)
if err != nil {
return "", err
}
parameters := url.Values{}
parameters.Add("grant_type", "access_token")
parameters.Add("service", parsedURL.Hostname())
parameters.Add("access_token", armToken)
resp, err := http.PostForm(exchangeUrl, parameters)
if err != nil {
return "", fmt.Errorf("failed to send token exchange request: %w", err)
}
if resp.StatusCode != http.StatusOK {
var errors []acrError
decoder := json.NewDecoder(resp.Body)
if err = decoder.Decode(&errors); err == nil {
return "", fmt.Errorf("unexpected status code %d from exchnage request: errors:%s",
resp.StatusCode, errors)
}
return "", fmt.Errorf("unexpected status code %d from exchnage request", resp.StatusCode)
}
var tokenResp tokenResponse
decoder := json.NewDecoder(resp.Body)
if err = decoder.Decode(&tokenResp); err != nil {
return "", err
}
return tokenResp.RefreshToken, nil
}

View File

@ -70,6 +70,8 @@ func main() {
storageValueLogFileSize int64
concurrent int
awsAutoLogin bool
gcpAutoLogin bool
azureAutoLogin bool
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
@ -81,6 +83,8 @@ func main() {
flag.Int64Var(&storageValueLogFileSize, "storage-value-log-file-size", 1<<28, "Set the database's memory mapped value log file size in bytes. Effective memory usage is about two times this size.")
flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent resource reconciles.")
flag.BoolVar(&awsAutoLogin, "aws-autologin-for-ecr", false, "(AWS) Attempt to get credentials for images in Elastic Container Registry, when no secret is referenced")
flag.BoolVar(&gcpAutoLogin, "gcp-autologin-for-gcr", false, "(GCP) Attempt to get credentials for images in Google Container Registry, when no secret is referenced")
flag.BoolVar(&azureAutoLogin, "azure-autologin-for-acr", false, "(Azure) Attempt to get credentials for images in Azure Container Registry, when no secret is referenced")
clientOptions.BindFlags(flag.CommandLine)
logOptions.BindFlags(flag.CommandLine)
@ -148,6 +152,8 @@ func main() {
MetricsRecorder: metricsRecorder,
Database: db,
AwsAutoLogin: awsAutoLogin,
GcpAutoLogin: gcpAutoLogin,
AzureAutoLogin: azureAutoLogin,
}).SetupWithManager(mgr, controllers.ImageRepositoryReconcilerOptions{
MaxConcurrentReconciles: concurrent,
}); err != nil {