mirror of
https://github.com/drone-plugins/drone-docker.git
synced 2026-06-04 18:24:24 +08:00
ci-9739 - workload identity/OIDC support for GAR/GCR (#413)
* adds support for oidc access tokens gar/gcr
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user