github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/backend/remote-state/gcs/backend.go (about) 1 // Package gcs implements remote storage of state on Google Cloud Storage (GCS). 2 package gcs 3 4 import ( 5 "context" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "os" 10 "strings" 11 12 "cloud.google.com/go/storage" 13 "github.com/muratcelep/terraform/not-internal/backend" 14 "github.com/muratcelep/terraform/not-internal/httpclient" 15 "github.com/muratcelep/terraform/not-internal/legacy/helper/schema" 16 "golang.org/x/oauth2" 17 "google.golang.org/api/impersonate" 18 "google.golang.org/api/option" 19 ) 20 21 // Backend implements "backend".Backend for GCS. 22 // Input(), Validate() and Configure() are implemented by embedding *schema.Backend. 23 // State(), DeleteState() and States() are implemented explicitly. 24 type Backend struct { 25 *schema.Backend 26 27 storageClient *storage.Client 28 storageContext context.Context 29 30 bucketName string 31 prefix string 32 33 encryptionKey []byte 34 } 35 36 func New() backend.Backend { 37 b := &Backend{} 38 b.Backend = &schema.Backend{ 39 ConfigureFunc: b.configure, 40 Schema: map[string]*schema.Schema{ 41 "bucket": { 42 Type: schema.TypeString, 43 Required: true, 44 Description: "The name of the Google Cloud Storage bucket", 45 }, 46 47 "prefix": { 48 Type: schema.TypeString, 49 Optional: true, 50 Description: "The directory where state files will be saved inside the bucket", 51 }, 52 53 "credentials": { 54 Type: schema.TypeString, 55 Optional: true, 56 Description: "Google Cloud JSON Account Key", 57 Default: "", 58 }, 59 60 "access_token": { 61 Type: schema.TypeString, 62 Optional: true, 63 DefaultFunc: schema.MultiEnvDefaultFunc([]string{ 64 "GOOGLE_OAUTH_ACCESS_TOKEN", 65 }, nil), 66 Description: "An OAuth2 token used for GCP authentication", 67 }, 68 69 "impersonate_service_account": { 70 Type: schema.TypeString, 71 Optional: true, 72 DefaultFunc: schema.MultiEnvDefaultFunc([]string{ 73 "GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", 74 }, nil), 75 Description: "The service account to impersonate for all Google API Calls", 76 }, 77 78 "impersonate_service_account_delegates": { 79 Type: schema.TypeList, 80 Optional: true, 81 Description: "The delegation chain for the impersonated service account", 82 Elem: &schema.Schema{Type: schema.TypeString}, 83 }, 84 85 "encryption_key": { 86 Type: schema.TypeString, 87 Optional: true, 88 Description: "A 32 byte base64 encoded 'customer supplied encryption key' used to encrypt all state.", 89 Default: "", 90 }, 91 }, 92 } 93 94 return b 95 } 96 97 func (b *Backend) configure(ctx context.Context) error { 98 if b.storageClient != nil { 99 return nil 100 } 101 102 // ctx is a background context with the backend config added. 103 // Since no context is passed to remoteClient.Get(), .Lock(), etc. but 104 // one is required for calling the GCP API, we're holding on to this 105 // context here and re-use it later. 106 b.storageContext = ctx 107 108 data := schema.FromContextBackendConfig(b.storageContext) 109 110 b.bucketName = data.Get("bucket").(string) 111 b.prefix = strings.TrimLeft(data.Get("prefix").(string), "/") 112 if b.prefix != "" && !strings.HasSuffix(b.prefix, "/") { 113 b.prefix = b.prefix + "/" 114 } 115 116 var opts []option.ClientOption 117 var credOptions []option.ClientOption 118 119 // Add credential source 120 var creds string 121 var tokenSource oauth2.TokenSource 122 123 if v, ok := data.GetOk("access_token"); ok { 124 tokenSource = oauth2.StaticTokenSource(&oauth2.Token{ 125 AccessToken: v.(string), 126 }) 127 } else if v, ok := data.GetOk("credentials"); ok { 128 creds = v.(string) 129 } else if v := os.Getenv("GOOGLE_BACKEND_CREDENTIALS"); v != "" { 130 creds = v 131 } else { 132 creds = os.Getenv("GOOGLE_CREDENTIALS") 133 } 134 135 if tokenSource != nil { 136 credOptions = append(credOptions, option.WithTokenSource(tokenSource)) 137 } else if creds != "" { 138 139 // to mirror how the provider works, we accept the file path or the contents 140 contents, err := backend.ReadPathOrContents(creds) 141 if err != nil { 142 return fmt.Errorf("Error loading credentials: %s", err) 143 } 144 145 if !json.Valid([]byte(contents)) { 146 return fmt.Errorf("the string provided in credentials is neither valid json nor a valid file path") 147 } 148 149 credOptions = append(credOptions, option.WithCredentialsJSON([]byte(contents))) 150 } 151 152 // Service Account Impersonation 153 if v, ok := data.GetOk("impersonate_service_account"); ok { 154 ServiceAccount := v.(string) 155 var delegates []string 156 157 if v, ok := data.GetOk("impersonate_service_account_delegates"); ok { 158 d := v.([]interface{}) 159 if len(delegates) > 0 { 160 delegates = make([]string, len(d)) 161 } 162 for _, delegate := range d { 163 delegates = append(delegates, delegate.(string)) 164 } 165 } 166 167 ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ 168 TargetPrincipal: ServiceAccount, 169 Scopes: []string{storage.ScopeReadWrite}, 170 Delegates: delegates, 171 }, credOptions...) 172 173 if err != nil { 174 return err 175 } 176 177 opts = append(opts, option.WithTokenSource(ts)) 178 179 } else { 180 opts = append(opts, credOptions...) 181 } 182 183 opts = append(opts, option.WithUserAgent(httpclient.UserAgentString())) 184 client, err := storage.NewClient(b.storageContext, opts...) 185 if err != nil { 186 return fmt.Errorf("storage.NewClient() failed: %v", err) 187 } 188 189 b.storageClient = client 190 191 key := data.Get("encryption_key").(string) 192 if key == "" { 193 key = os.Getenv("GOOGLE_ENCRYPTION_KEY") 194 } 195 196 if key != "" { 197 kc, err := backend.ReadPathOrContents(key) 198 if err != nil { 199 return fmt.Errorf("Error loading encryption key: %s", err) 200 } 201 202 // The GCS client expects a customer supplied encryption key to be 203 // passed in as a 32 byte long byte slice. The byte slice is base64 204 // encoded before being passed to the API. We take a base64 encoded key 205 // to remain consistent with the GCS docs. 206 // https://cloud.google.com/storage/docs/encryption#customer-supplied 207 // https://github.com/GoogleCloudPlatform/google-cloud-go/blob/def681/storage/storage.go#L1181 208 k, err := base64.StdEncoding.DecodeString(kc) 209 if err != nil { 210 return fmt.Errorf("Error decoding encryption key: %s", err) 211 } 212 b.encryptionKey = k 213 } 214 215 return nil 216 }