@@ -11,11 +11,15 @@ import (
1111 "strconv"
1212 "strings"
1313
14+ docker "github.com/drone-plugins/drone-docker"
15+
1416 "github.com/joho/godotenv"
1517 "github.com/sirupsen/logrus"
18+ "golang.org/x/oauth2"
1619 "golang.org/x/oauth2/google"
17-
18- docker "github.com/drone-plugins/drone-docker"
20+ "google.golang.org/api/iamcredentials/v1"
21+ "google.golang.org/api/option"
22+ "google.golang.org/api/sts/v1"
1923)
2024
2125type Config struct {
@@ -25,11 +29,21 @@ type Config struct {
2529 WorkloadIdentity bool
2630 Username string
2731 RegistryType string
32+ AccessToken string
33+ }
34+
35+ type staticTokenSource struct {
36+ token * oauth2.Token
37+ }
38+
39+ func (s * staticTokenSource ) Token () (* oauth2.Token , error ) {
40+ return s .token , nil
2841}
2942
3043func loadConfig () Config {
3144 // Default username
3245 username := "_json_key"
46+ var config Config
3347
3448 // Load env-file if it exists
3549 if env := os .Getenv ("PLUGIN_ENV_FILE" ); env != "" {
@@ -38,18 +52,36 @@ func loadConfig() Config {
3852 }
3953 }
4054
55+ idToken := getenv ("PLUGIN_OIDC_TOKEN_ID" )
56+ projectId := getenv ("PLUGIN_PROJECT_NUMBER" )
57+ poolId := getenv ("PLUGIN_POOL_ID" )
58+ providerId := getenv ("PLUGIN_PROVIDER_ID" )
59+ serviceAccountEmail := getenv ("PLUGIN_SERVICE_ACCOUNT_EMAIL" )
60+
61+ if idToken != "" && projectId != "" && poolId != "" && providerId != "" && serviceAccountEmail != "" {
62+ federalToken , err := getFederalToken (idToken , projectId , poolId , providerId )
63+ if err != nil {
64+ logrus .Fatalf ("Error (getFederalToken): %s" , err )
65+ }
66+ accessToken , err := getGoogleCloudAccessToken (federalToken , serviceAccountEmail )
67+ if err != nil {
68+ logrus .Fatalf ("Error (getGoogleCloudAccessToken): %s" , err )
69+ }
70+ config .AccessToken = accessToken
71+ } else {
72+ password := getenv (
73+ "PLUGIN_JSON_KEY" ,
74+ "GCR_JSON_KEY" ,
75+ "GOOGLE_CREDENTIALS" ,
76+ "TOKEN" ,
77+ )
78+ config .WorkloadIdentity = parseBoolOrDefault (false , getenv ("PLUGIN_WORKLOAD_IDENTITY" ))
79+ config .Username , config .Password = setUsernameAndPassword (username , password , config .WorkloadIdentity )
80+ }
81+
4182 location := getenv ("PLUGIN_LOCATION" )
4283 repo := getenv ("PLUGIN_REPO" )
4384
44- password := getenv (
45- "PLUGIN_JSON_KEY" ,
46- "GCR_JSON_KEY" ,
47- "GOOGLE_CREDENTIALS" ,
48- "TOKEN" ,
49- )
50- workloadIdentity := parseBoolOrDefault (false , getenv ("PLUGIN_WORKLOAD_IDENTITY" ))
51- username , password = setUsernameAndPassword (username , password , workloadIdentity )
52-
5385 registryType := getenv ("PLUGIN_REGISTRY_TYPE" )
5486 if registryType == "" {
5587 registryType = "GCR"
@@ -73,24 +105,23 @@ func loadConfig() Config {
73105 if ! strings .HasPrefix (repo , registry ) {
74106 repo = path .Join (registry , repo )
75107 }
76-
77- return Config {
78- Repo : repo ,
79- Registry : registry ,
80- Password : password ,
81- WorkloadIdentity : workloadIdentity ,
82- Username : username ,
83- RegistryType : registryType ,
84- }
108+ config .Repo = repo
109+ config .Registry = registry
110+ config .RegistryType = registryType
111+ return config
85112}
86113
87114func main () {
88115 config := loadConfig ()
116+ if config .AccessToken != "" {
117+ os .Setenv ("ACCESS_TOKEN" , config .AccessToken )
118+ } else if config .Username != "" && config .Password != "" {
119+ os .Setenv ("DOCKER_USERNAME" , config .Username )
120+ os .Setenv ("DOCKER_PASSWORD" , config .Password )
121+ }
89122
90123 os .Setenv ("PLUGIN_REPO" , config .Repo )
91124 os .Setenv ("PLUGIN_REGISTRY" , config .Registry )
92- os .Setenv ("DOCKER_USERNAME" , config .Username )
93- os .Setenv ("DOCKER_PASSWORD" , config .Password )
94125 os .Setenv ("PLUGIN_REGISTRY_TYPE" , config .RegistryType )
95126
96127 // invoke the base docker plugin binary
@@ -152,3 +183,49 @@ func getenv(key ...string) (s string) {
152183 }
153184 return
154185}
186+
187+ func getFederalToken (idToken , projectNumber , poolId , providerId string ) (string , error ) {
188+ ctx := context .Background ()
189+ stsService , err := sts .NewService (ctx , option .WithoutAuthentication ())
190+ if err != nil {
191+ return "" , err
192+ }
193+ audience := fmt .Sprintf ("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s" , projectNumber , poolId , providerId )
194+ tokenRequest := & sts.GoogleIdentityStsV1ExchangeTokenRequest {
195+ GrantType : "urn:ietf:params:oauth:grant-type:token-exchange" ,
196+ SubjectToken : idToken ,
197+ Audience : audience ,
198+ Scope : "https://www.googleapis.com/auth/cloud-platform" ,
199+ RequestedTokenType : "urn:ietf:params:oauth:token-type:access_token" ,
200+ SubjectTokenType : "urn:ietf:params:oauth:token-type:id_token" ,
201+ }
202+ tokenResponse , err := stsService .V1 .Token (tokenRequest ).Do ()
203+ if err != nil {
204+ return "" , err
205+ }
206+
207+ return tokenResponse .AccessToken , nil
208+ }
209+
210+ func getGoogleCloudAccessToken (federatedToken string , serviceAccountEmail string ) (string , error ) {
211+ ctx := context .Background ()
212+ tokenSource := & staticTokenSource {
213+ token : & oauth2.Token {AccessToken : federatedToken },
214+ }
215+ service , err := iamcredentials .NewService (ctx , option .WithTokenSource (tokenSource ))
216+ if err != nil {
217+ return "" , err
218+ }
219+
220+ name := "projects/-/serviceAccounts/" + serviceAccountEmail
221+ rb := & iamcredentials.GenerateAccessTokenRequest {
222+ Scope : []string {"https://www.googleapis.com/auth/cloud-platform" },
223+ }
224+
225+ resp , err := service .Projects .ServiceAccounts .GenerateAccessToken (name , rb ).Do ()
226+ if err != nil {
227+ return "" , err
228+ }
229+
230+ return resp .AccessToken , nil
231+ }
0 commit comments