1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00

Support GOOGLE_OAUTH_ACCESS_TOKEN for GCP

Co-authored-by: Maren Sofie Ringsby <marensofieringsby@gmail.com>
Co-authored-by: Matheus Pimenta <matheuscscp@gmail.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
Signed-off-by: Hidde Beydals <hidde@hhh.computer>
This commit is contained in:
Hidde Beydals
2025-03-30 14:06:09 +01:00
parent 2e22c04f7f
commit ecf3194d4a
3 changed files with 81 additions and 11 deletions

View File

@@ -266,8 +266,12 @@ It is also possible to use ``updatekeys``, when adding or removing age recipient
Encrypting using GCP KMS
~~~~~~~~~~~~~~~~~~~~~~~~
GCP KMS uses `Application Default Credentials
<https://developers.google.com/identity/protocols/application-default-credentials>`_.
GCP KMS has support for authorization with the use of `Application Default Credentials
<https://developers.google.com/identity/protocols/application-default-credentials>`_ and using an OAuth 2.0 token.
Application default credentials precedes the use of access token.
Using Application Default Credentials you can authorize by doing this:
If you already logged in using
.. code:: sh
@@ -280,6 +284,18 @@ you can enable application default credentials using the sdk:
$ gcloud auth application-default login
Using OAauth tokens you can authorize by doing this:
.. code:: sh
$ export GOOGLE_OAUTH_ACCESS_TOKEN=<your access token>
Or if you are logged in you can authorize by generating an access token:
.. code:: sh
$ export GOOGLE_OAUTH_ACCESS_TOKEN="$(gcloud auth print-access-token)"
Encrypting/decrypting with GCP KMS requires a KMS ResourceID. You can use the
cloud console the get the ResourceID or you can create one using the gcloud
sdk:

View File

@@ -24,6 +24,9 @@ const (
// a path to a credentials file, or directly as the variable's value in JSON
// format.
SopsGoogleCredentialsEnv = "GOOGLE_CREDENTIALS"
// SopsGoogleCredentialsOAuthTokenEnv is the environment variable used for the
// GCP OAuth 2.0 Token.
SopsGoogleCredentialsOAuthTokenEnv = "GOOGLE_OAUTH_ACCESS_TOKEN"
// KeyTypeIdentifier is the string used to identify a GCP KMS MasterKey.
KeyTypeIdentifier = "gcp_kms"
)
@@ -245,12 +248,19 @@ func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
default:
credentials, err := getGoogleCredentials()
if err != nil {
return nil, err
return nil, fmt.Errorf("credentials: failed to obtain credentials from %q: %w", SopsGoogleCredentialsEnv, err)
}
if credentials != nil {
opts = append(opts, option.WithCredentialsJSON(credentials))
break
}
if atCredentials := getGoogleOAuthTokenFromEnv(); atCredentials != nil {
opts = append(opts, option.WithTokenSource(atCredentials))
break
}
}
if key.grpcConn != nil {
opts = append(opts, option.WithGRPCConn(key.grpcConn))
}
@@ -266,8 +276,8 @@ func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
// getGoogleCredentials returns the SopsGoogleCredentialsEnv variable, as
// either the file contents of the path of a credentials file, or as value in
// JSON format. It returns an error if the file cannot be read, and may return
// a nil byte slice if no value is set.
// JSON format.
// It returns an error and a nil byte slice if the file cannot be read.
func getGoogleCredentials() ([]byte, error) {
if defaultCredentials, ok := os.LookupEnv(SopsGoogleCredentialsEnv); ok && len(defaultCredentials) > 0 {
if _, err := os.Stat(defaultCredentials); err == nil {
@@ -277,3 +287,16 @@ func getGoogleCredentials() ([]byte, error) {
}
return nil, nil
}
// getGoogleOAuthTokenFromEnv returns the SopsGoogleCredentialsOauthTokenEnv variable,
// as the OAauth 2.0 token.
// It returns an error and a nil byte slice if the envrionment variable is not set.
func getGoogleOAuthTokenFromEnv() oauth2.TokenSource {
if token, ok := os.LookupEnv(SopsGoogleCredentialsOAuthTokenEnv); ok && len(token) > 0 {
tokenSource := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
return tokenSource
}
return nil
}

View File

@@ -61,8 +61,9 @@ func TestMasterKey_Encrypt(t *testing.T) {
})
key := MasterKey{
grpcConn: newGRPCServer("0"),
ResourceID: testResourceID,
grpcConn: newGRPCServer("0"),
ResourceID: testResourceID,
credentialJSON: []byte("arbitrary credentials"),
}
err := key.Encrypt([]byte("encrypt"))
assert.NoError(t, err)
@@ -88,9 +89,10 @@ func TestMasterKey_Decrypt(t *testing.T) {
Plaintext: []byte(decryptedData),
})
key := MasterKey{
grpcConn: newGRPCServer("0"),
ResourceID: testResourceID,
EncryptedKey: "encryptedKey",
grpcConn: newGRPCServer("0"),
ResourceID: testResourceID,
EncryptedKey: "encryptedKey",
credentialJSON: []byte("arbitrary credentials"),
}
data, err := key.Decrypt()
assert.NoError(t, err)
@@ -124,7 +126,7 @@ func TestMasterKey_ToMap(t *testing.T) {
}, key.ToMap())
}
func TestMasterKey_createCloudKMSService(t *testing.T) {
func TestMasterKey_createCloudKMSService_withCredentialsFile(t *testing.T) {
tests := []struct {
key MasterKey
errString string
@@ -144,6 +146,12 @@ func TestMasterKey_createCloudKMSService(t *testing.T) {
"type": "authorized_user"}`),
},
},
{
key: MasterKey{
ResourceID: testResourceID,
},
errString: `credentials: failed to obtain credentials from "SOPS_GOOGLE_CREDENTIALS"`,
},
}
for _, tt := range tests {
@@ -157,6 +165,29 @@ func TestMasterKey_createCloudKMSService(t *testing.T) {
}
}
func TestMasterKey_createCloudKMSService_withOauthToken(t *testing.T) {
t.Setenv(SopsGoogleCredentialsOAuthTokenEnv, "token")
masterKey := MasterKey{
ResourceID: testResourceID,
}
_, err := masterKey.newKMSClient()
assert.NoError(t, err)
}
func TestMasterKey_createCloudKMSService_withoutCredentials(t *testing.T) {
masterKey := MasterKey{
ResourceID: testResourceID,
}
_, err := masterKey.newKMSClient()
assert.Error(t, err)
assert.ErrorContains(t, err, "credentials: could not find default credentials")
}
func newGRPCServer(port string) *grpc.ClientConn {
serv := grpc.NewServer()
kmspb.RegisterKeyManagementServiceServer(serv, &mockKeyManagement)