client/confidentialspace/confidentialspace.go (75 lines of code) (raw):
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package confidentialspace defines methods for integration with Confidential Space.
package confidentialspace
import (
"errors"
"fmt"
"os"
"regexp"
configpb "github.com/GoogleCloudPlatform/stet/proto/config_go_proto"
glog "github.com/golang/glog"
)
const (
tokenFilePath = "/run/container_launcher/"
tokenFileName = "attestation_verifier_claims_token"
audiencePrefix = "//iam.googleapis.com/"
impersonationURLFmt = `"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%v:generateAccessToken",`
credentialConfigFmt = `{
"type": "external_account",
"audience": "%s",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
%s
"credential_source": {
"file": "%s"
}
}`
)
// Config wraps ConfidentialSpaceConfigs for STET.
type Config struct {
inner *configpb.ConfidentialSpaceConfigs
tokenFile string
tokenFileFound bool
}
// NewConfig initializes a Config containing the provided 'config' proto.
func NewConfig(config *configpb.ConfidentialSpaceConfigs) *Config {
tokenFile := tokenFilePath + tokenFileName
return NewConfigWithTokenFile(config, tokenFile)
}
// NewConfigWithTokenFile initializes a Config containing the provided 'config' proto and
// tokenFile. This allows our tests to use a custom tokenFile path.
func NewConfigWithTokenFile(config *configpb.ConfidentialSpaceConfigs, tokenFile string) *Config {
cfg := &Config{
inner: config,
tokenFile: tokenFile,
tokenFileFound: fileExists(tokenFile),
}
return cfg
}
// fileExists checks whether the provided file exists. If the os.Stat operation fails for a reason
// other than os.ErrNotExist (meaning the file may or may not exist), log the error and return
// false.
func fileExists(filepath string) bool {
_, err := os.Stat(filepath)
// If the err is not os.ErrNotExist, log it.
if err != nil && !errors.Is(err, os.ErrNotExist) {
glog.Errorf("error looking for file: %v", err)
}
return err == nil
}
// CreateJSONCredentials returns a JSON credential config containing the provided info.
func CreateJSONCredentials(cred *configpb.KekCredentialConfig, sourceFile string) string {
aud := audiencePrefix + cred.WipName
impersonationURL := ""
if len(cred.ServiceAccount) > 0 {
impersonationURL = fmt.Sprintf(impersonationURLFmt, cred.ServiceAccount)
}
return fmt.Sprintf(credentialConfigFmt, aud, impersonationURL, sourceFile)
}
// FindMatchingCredentials searches the config for an entry with a URI pattern and credential mode
// matching 'kekURI' and 'mode' respectively.
// If a match is found, it returns a Credential Config JSON containing the information in the
// matching config.
func (c *Config) FindMatchingCredentials(kekURI string, mode configpb.CredentialMode) string {
// Return empty if not in Confidential Space.
if !c.tokenFileFound {
return ""
}
for _, cred := range c.inner.GetKekCredentials() {
// Check the mode matches.
if cred.GetMode() == configpb.CredentialMode_DEFAULT_ENCRYPT_AND_DECRYPT_MODE || cred.GetMode() == mode {
// Check the KEK pattern matches.
match, err := regexp.MatchString(cred.GetKekUriPattern(), kekURI)
// If there was an error, log and move to the next set of credentials.
if err != nil {
glog.Errorf("Invalid KEK URI pattern: %s", cred.GetKekUriPattern())
continue
}
if match {
return CreateJSONCredentials(cred, c.tokenFile)
}
}
}
return ""
}