Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
29 changes: 26 additions & 3 deletions gcpkms/keysource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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))
}
Expand All @@ -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 {
Expand All @@ -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
}
43 changes: 37 additions & 6 deletions gcpkms/keysource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
Loading