ci-9739 - workload identity/OIDC support for GAR/GCR (#413)

* adds support for oidc access tokens gar/gcr
This commit is contained in:
Eoin McAfee
2023-10-23 10:11:18 +01:00
committed by GitHub
parent c354cd6a8d
commit 757a756d1a
6 changed files with 314 additions and 41 deletions
+11 -5
View File
@@ -292,6 +292,11 @@ func main() {
Usage: "registry type",
EnvVar: "PLUGIN_REGISTRY_TYPE",
},
cli.StringFlag{
Name: "access-token",
Usage: "access token",
EnvVar: "ACCESS_TOKEN",
},
}
if err := app.Run(os.Args); err != nil {
@@ -309,11 +314,12 @@ func run(c *cli.Context) error {
Dryrun: c.Bool("dry-run"),
Cleanup: c.BoolT("docker.purge"),
Login: docker.Login{
Registry: c.String("docker.registry"),
Username: c.String("docker.username"),
Password: c.String("docker.password"),
Email: c.String("docker.email"),
Config: c.String("docker.config"),
Registry: c.String("docker.registry"),
Username: c.String("docker.username"),
Password: c.String("docker.password"),
Email: c.String("docker.email"),
Config: c.String("docker.config"),
AccessToken: c.String("access-token"),
},
CardPath: c.String("drone-card-path"),
ArtifactFile: c.String("artifact-file"),
+99 -22
View File
@@ -11,11 +11,15 @@ import (
"strconv"
"strings"
docker "github.com/drone-plugins/drone-docker"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
docker "github.com/drone-plugins/drone-docker"
"google.golang.org/api/iamcredentials/v1"
"google.golang.org/api/option"
"google.golang.org/api/sts/v1"
)
type Config struct {
@@ -25,11 +29,21 @@ type Config struct {
WorkloadIdentity bool
Username string
RegistryType string
AccessToken string
}
type staticTokenSource struct {
token *oauth2.Token
}
func (s *staticTokenSource) Token() (*oauth2.Token, error) {
return s.token, nil
}
func loadConfig() Config {
// Default username
username := "_json_key"
var config Config
// Load env-file if it exists
if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" {
@@ -38,18 +52,36 @@ func loadConfig() Config {
}
}
idToken := getenv("PLUGIN_OIDC_TOKEN_ID")
projectId := getenv("PLUGIN_PROJECT_NUMBER")
poolId := getenv("PLUGIN_POOL_ID")
providerId := getenv("PLUGIN_PROVIDER_ID")
serviceAccountEmail := getenv("PLUGIN_SERVICE_ACCOUNT_EMAIL")
if idToken != "" && projectId != "" && poolId != "" && providerId != "" && serviceAccountEmail != "" {
federalToken, err := getFederalToken(idToken, projectId, poolId, providerId)
if err != nil {
logrus.Fatalf("Error (getFederalToken): %s", err)
}
accessToken, err := getGoogleCloudAccessToken(federalToken, serviceAccountEmail)
if err != nil {
logrus.Fatalf("Error (getGoogleCloudAccessToken): %s", err)
}
config.AccessToken = accessToken
} else {
password := getenv(
"PLUGIN_JSON_KEY",
"GCR_JSON_KEY",
"GOOGLE_CREDENTIALS",
"TOKEN",
)
config.WorkloadIdentity = parseBoolOrDefault(false, getenv("PLUGIN_WORKLOAD_IDENTITY"))
config.Username, config.Password = setUsernameAndPassword(username, password, config.WorkloadIdentity)
}
location := getenv("PLUGIN_LOCATION")
repo := getenv("PLUGIN_REPO")
password := getenv(
"PLUGIN_JSON_KEY",
"GCR_JSON_KEY",
"GOOGLE_CREDENTIALS",
"TOKEN",
)
workloadIdentity := parseBoolOrDefault(false, getenv("PLUGIN_WORKLOAD_IDENTITY"))
username, password = setUsernameAndPassword(username, password, workloadIdentity)
registryType := getenv("PLUGIN_REGISTRY_TYPE")
if registryType == "" {
registryType = "GCR"
@@ -73,24 +105,23 @@ func loadConfig() Config {
if !strings.HasPrefix(repo, registry) {
repo = path.Join(registry, repo)
}
return Config{
Repo: repo,
Registry: registry,
Password: password,
WorkloadIdentity: workloadIdentity,
Username: username,
RegistryType: registryType,
}
config.Repo = repo
config.Registry = registry
config.RegistryType = registryType
return config
}
func main() {
config := loadConfig()
if config.AccessToken != "" {
os.Setenv("ACCESS_TOKEN", config.AccessToken)
} else if config.Username != "" && config.Password != "" {
os.Setenv("DOCKER_USERNAME", config.Username)
os.Setenv("DOCKER_PASSWORD", config.Password)
}
os.Setenv("PLUGIN_REPO", config.Repo)
os.Setenv("PLUGIN_REGISTRY", config.Registry)
os.Setenv("DOCKER_USERNAME", config.Username)
os.Setenv("DOCKER_PASSWORD", config.Password)
os.Setenv("PLUGIN_REGISTRY_TYPE", config.RegistryType)
// invoke the base docker plugin binary
@@ -152,3 +183,49 @@ func getenv(key ...string) (s string) {
}
return
}
func getFederalToken(idToken, projectNumber, poolId, providerId string) (string, error) {
ctx := context.Background()
stsService, err := sts.NewService(ctx, option.WithoutAuthentication())
if err != nil {
return "", err
}
audience := fmt.Sprintf("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s", projectNumber, poolId, providerId)
tokenRequest := &sts.GoogleIdentityStsV1ExchangeTokenRequest{
GrantType: "urn:ietf:params:oauth:grant-type:token-exchange",
SubjectToken: idToken,
Audience: audience,
Scope: "https://www.googleapis.com/auth/cloud-platform",
RequestedTokenType: "urn:ietf:params:oauth:token-type:access_token",
SubjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
}
tokenResponse, err := stsService.V1.Token(tokenRequest).Do()
if err != nil {
return "", err
}
return tokenResponse.AccessToken, nil
}
func getGoogleCloudAccessToken(federatedToken string, serviceAccountEmail string) (string, error) {
ctx := context.Background()
tokenSource := &staticTokenSource{
token: &oauth2.Token{AccessToken: federatedToken},
}
service, err := iamcredentials.NewService(ctx, option.WithTokenSource(tokenSource))
if err != nil {
return "", err
}
name := "projects/-/serviceAccounts/" + serviceAccountEmail
rb := &iamcredentials.GenerateAccessTokenRequest{
Scope: []string{"https://www.googleapis.com/auth/cloud-platform"},
}
resp, err := service.Projects.ServiceAccounts.GenerateAccessToken(name, rb).Do()
if err != nil {
return "", err
}
return resp.AccessToken, nil
}