diff --git a/libs/go/sia/options/mockgcpprovider.go b/libs/go/sia/options/mockgcpprovider.go index 41415af6f6f..10d180d308d 100644 --- a/libs/go/sia/options/mockgcpprovider.go +++ b/libs/go/sia/options/mockgcpprovider.go @@ -25,7 +25,7 @@ func (tp MockGCPProvider) GetName() string { } // GetHostname returns the hostname as per the provider -func (tp MockGCPProvider) GetHostname() string { +func (tp MockGCPProvider) GetHostname(bool) string { return tp.Hostname } diff --git a/libs/go/sia/options/options.go b/libs/go/sia/options/options.go index 7a58f0b7dea..8926a32571e 100644 --- a/libs/go/sia/options/options.go +++ b/libs/go/sia/options/options.go @@ -22,7 +22,6 @@ import ( "errors" "fmt" legacy "github.com/AthenZ/athenz/libs/go/sia/aws/options" - "log" "os" "strings" @@ -524,7 +523,19 @@ func InitEnvConfig(config *Config, provider provider.Provider) (*Config, *Config } } + var configRoles map[string]ConfigRole + rolesEnv := os.Getenv("ATHENZ_SIA_ACCOUNT_ROLES") + if rolesEnv != "" { + err := json.Unmarshal([]byte(rolesEnv), &configRoles) + if err != nil { + return config, nil, fmt.Errorf("unable to parse athenz account roles '%s': %v", rolesEnv, err) + } + } + config.Roles = configRoles + + var configAccount *ConfigAccount if isAWSEnvironment(provider) { + roleArn := os.Getenv("ATHENZ_SIA_IAM_ROLE_ARN") if roleArn == "" { return config, nil, fmt.Errorf("athenz role arn env variable not configured") @@ -536,21 +547,17 @@ func InitEnvConfig(config *Config, provider provider.Provider) (*Config, *Config if account == "" || domain == "" || service == "" { return config, nil, fmt.Errorf("invalid role arn - missing components: %s", roleArn) } - - var configRoles map[string]ConfigRole - rolesEnv := os.Getenv("ATHENZ_SIA_ACCOUNT_ROLES") - if rolesEnv != "" { - err = json.Unmarshal([]byte(rolesEnv), &configRoles) - if err != nil { - return config, nil, fmt.Errorf("unable to parse athenz account roles '%s': %v", rolesEnv, err) - } + if config.Account == "" { + config.Account = account + } + if config.Domain == "" { + config.Domain = domain + } + if config.Service == "" { + config.Service = service } - config.Account = account - config.Domain = domain - config.Service = service - config.Roles = configRoles - return config, &ConfigAccount{ + configAccount = &ConfigAccount{ Account: account, Domain: domain, Service: service, @@ -559,15 +566,22 @@ func InitEnvConfig(config *Config, provider provider.Provider) (*Config, *Config Threshold: config.Threshold, SshThreshold: config.SshThreshold, OmitDomain: omitDomain, - }, nil - } else { - // TODO add gcp specific new env var names + } + + } else if isGCPEnvironment(provider) { + + if config.Domain == "" { + config.Domain = os.Getenv("ATHENZ_SIA_DOMAIN_NAME") + } + if config.Service == "" { + config.Service = os.Getenv("ATHENZ_SIA_SERVICE_NAME") + } if config.Domain == "" || config.Service == "" { return config, nil, fmt.Errorf("one or more required settings can not be retrieved from env variables") } } - return config, nil, nil + return config, configAccount, nil } func InitAccessProfileEnvConfig() (*AccessProfileConfig, error) { diff --git a/libs/go/sia/options/options_test.go b/libs/go/sia/options/options_test.go index 82c7205f11c..de9e2a26c20 100644 --- a/libs/go/sia/options/options_test.go +++ b/libs/go/sia/options/options_test.go @@ -707,7 +707,7 @@ func idCommandId(arg string) int { return id } -func TestInitEnvConfig(t *testing.T) { +func TestInitEnvConfigAwsProvider(t *testing.T) { os.Setenv("ATHENZ_SIA_SANDNS_WILDCARD", "true") os.Setenv("ATHENZ_SIA_SANDNS_HOSTNAME", "true") os.Setenv("ATHENZ_SIA_HOSTNAME_SUFFIX", "zts.athenz.cloud") @@ -782,6 +782,80 @@ func TestInitEnvConfig(t *testing.T) { os.Clearenv() } +func TestInitEnvConfigGcpProvider(t *testing.T) { + os.Setenv("ATHENZ_SIA_SANDNS_WILDCARD", "true") + os.Setenv("ATHENZ_SIA_SANDNS_HOSTNAME", "true") + os.Setenv("ATHENZ_SIA_HOSTNAME_SUFFIX", "zts.athenz.cloud") + os.Setenv("ATHENZ_SIA_REGIONAL_STS", "true") + os.Setenv("ATHENZ_SIA_GENERATE_ROLE_KEY", "true") + os.Setenv("ATHENZ_SIA_ROTATE_KEY", "false") + os.Setenv("ATHENZ_SIA_USER", "root") + os.Setenv("ATHENZ_SIA_GROUP", "nobody") + os.Setenv("ATHENZ_SIA_SDS_UDS_PATH", "/tmp/uds") + os.Setenv("ATHENZ_SIA_SDS_UDS_UID", "1336") + os.Setenv("ATHENZ_SIA_EXPIRY_TIME", "10001") + os.Setenv("ATHENZ_SIA_REFRESH_INTERVAL", "120") + os.Setenv("ATHENZ_SIA_ZTS_REGION", "us-west-3") + os.Setenv("ATHENZ_SIA_DROP_PRIVILEGES", "true") + os.Setenv("ATHENZ_SIA_FILE_DIRECT_UPDATE", "true") + os.Setenv("ATHENZ_SIA_ACCOUNT_ROLES", "{\"sports:role.readers\":{\"service\":\"api\"},\"sports:role.writers\":{\"user\": \"nobody\"}}") + os.Setenv("ATHENZ_SIA_ACCESS_TOKENS", "{\"sports/api\":{\"roles\":[\"sports:role.readers\"],\"expires_in\":3600}}") + os.Setenv("ATHENZ_SIA_KEY_DIR", "/var/athenz/keys") + os.Setenv("ATHENZ_SIA_CERT_DIR", "/var/athenz/certs") + os.Setenv("ATHENZ_SIA_TOKEN_DIR", "/var/athenz/tokens") + os.Setenv("ATHENZ_SIA_SSH_PRINCIPALS", "host1.athenz.io") + os.Setenv("ATHENZ_SIA_FAIL_COUNT_FOR_EXIT", "10") + os.Setenv("ATHENZ_SIA_SPIFFE_TRUST_DOMAIN", "athenz.io") + os.Setenv("ATHENZ_SIA_STORE_TOKEN_OPTION", "2") + os.Setenv("ATHENZ_SIA_OMIT_DOMAIN", "true") + os.Setenv("ATHENZ_SIA_SANDNS_X509_CNAMES", "svc1.athenz.io,svc2.athenz.io") + os.Setenv("ATHENZ_SIA_DOMAIN_NAME", "athenz") + os.Setenv("ATHENZ_SIA_SERVICE_NAME", "api") + + provider := MockGCPProvider{ + Name: fmt.Sprintf("athenz.gcp.us-west-2"), + Hostname: "", + } + cfg, cfgAccount, err := InitEnvConfig(nil, provider) + require.Nilf(t, err, "error should be empty, error: %v", err) + require.Nilf(t, cfgAccount, "cfgAccount should be nil") + assert.True(t, cfg.SanDnsWildcard) + assert.True(t, cfg.SanDnsHostname) + assert.True(t, cfg.UseRegionalSTS) + assert.True(t, cfg.GenerateRoleKey) + assert.True(t, cfg.FileDirectUpdate) + assert.False(t, cfg.RotateKey) + assert.Equal(t, "root", cfg.User) + assert.Equal(t, "nobody", cfg.Group) + assert.Equal(t, "/tmp/uds", cfg.SDSUdsPath) + assert.Equal(t, 1336, cfg.SDSUdsUid) + assert.Equal(t, 10001, cfg.ExpiryTime) + assert.Equal(t, 120, cfg.RefreshInterval) + assert.Equal(t, "us-west-3", cfg.ZTSRegion) + assert.True(t, cfg.DropPrivileges) + assert.Equal(t, "/var/athenz/keys", cfg.SiaKeyDir) + assert.Equal(t, "/var/athenz/certs", cfg.SiaCertDir) + assert.Equal(t, "/var/athenz/tokens", cfg.SiaTokenDir) + assert.Equal(t, "zts.athenz.cloud", cfg.HostnameSuffix) + assert.Equal(t, "athenz.io", cfg.SpiffeTrustDomain) + assert.Equal(t, "svc1.athenz.io,svc2.athenz.io", cfg.SanDnsX509Cnames) + + assert.Equal(t, 1, len(cfg.AccessTokens)) + assert.Equal(t, cfg.AccessTokens["sports/api"].Service, "") + assert.Equal(t, 1, len(cfg.AccessTokens["sports/api"].Roles)) + assert.Equal(t, "sports:role.readers", cfg.AccessTokens["sports/api"].Roles[0]) + assert.Equal(t, 3600, cfg.AccessTokens["sports/api"].Expiry) + + assert.Equal(t, "athenz", cfg.Domain) + assert.Equal(t, "api", cfg.Service) + assert.Equal(t, 2, len(cfg.Roles)) + assert.Equal(t, "host1.athenz.io", cfg.SshPrincipals) + assert.Equal(t, 10, cfg.FailCountForExit) + assert.Equal(t, 2, *cfg.StoreTokenOption) + + os.Clearenv() +} + func TestGetConfigWithSshHostKeyType(t *testing.T) { tests := map[string]struct { diff --git a/provider/gcp/sia-run/Makefile b/provider/gcp/sia-run/Makefile new file mode 100644 index 00000000000..0aa8a2d7a8d --- /dev/null +++ b/provider/gcp/sia-run/Makefile @@ -0,0 +1,73 @@ +GOPKGNAME:=github.com/AthenZ/athenz/provider/gcp/sia-run +export GOPATH ?= /tmp/go +export GOPRIVATE=github.com + +FMT_LOG=/tmp/fmt.log + +BUILD_VERSION:=development +CENTOS_VERSION:=7 + +all: build_darwin build_linux test + +local: build test + +darwin: build_darwin test + +linux: build_linux test + +build: build_darwin build_linux + +build_darwin: + @echo "Building darwin arm64 client with $(BUILD_VERSION)" + GOOS=darwin GOARCH=arm64 go install -ldflags "-X main.Version=$(BUILD_VERSION)" -v $(GOPKGNAME)/... + @echo "Building darwin amd64 client with $(BUILD_VERSION)" + GOOS=darwin GOARCH=amd64 go install -ldflags "-X main.Version=$(BUILD_VERSION)" -v $(GOPKGNAME)/... + +build_linux: + @echo "Building linux arm64 client with $(BUILD_VERSION)" + GOOS=linux GOARCH=arm64 go install -ldflags "-X main.Version=$(BUILD_VERSION)" -v $(GOPKGNAME)/... + @echo "Building linux client with $(BUILD_VERSION)" + GOOS=linux GOARCH=amd64 go install -ldflags "-X main.Version=$(BUILD_VERSION)" -v $(GOPKGNAME)/... + +vet: + go vet $(GOPKGNAME)/... + +fmt: + gofmt -d . >$(FMT_LOG) + @if [ -s $(FMT_LOG) ]; then echo gofmt FAIL; cat $(FMT_LOG); false; fi + +test: vet fmt + go test -v $(GOPKGNAME)/... + +clean: + go clean -i -x $(GOPKGNAME)/... + +custom.clean.post: + rm -rf $(GOPATH)/bin/{linux_amd64,linux_arm64}/{metamock,siad} + + +RPM_DIR := $(shell pwd)/rpm +RPM_VARS := +RPM_VARS += --define 'BIN_DIR $(GOPATH)/bin' +RPM_VARS += --define 'PACKAGE_VERSION $(BUILD_VERSION)' +RPM_VARS += --define 'RELEASE 1' +RPM_VARS += --define 'CENTOS_VERSION $(CENTOS_VERSION)' +RPM_VARS += --define '_topdir $(RPM_DIR)' + +package: + echo "rpmbuild $(RPM_VARS) -bb sia-run.spec" + find $(GOPATH)/bin + echo "siad version" + $(GOPATH)/bin/siad -version + rpmbuild $(RPM_VARS) -bb sia-run.spec + +ubuntu: + sed -i.bak s/SIA_PACKAGE_VERSION/$(PACKAGE_VERSION)/g debian/sia/DEBIAN/control + mkdir -p debian/sia/usr/lib/systemd/system/ + cp -fp $(GOPATH)/src/$(GOPKGNAME)/build/service/sia.service debian/sia/usr/lib/systemd/system/ + mkdir -p debian/sia/usr/sbin/ + cp -fp $(GOPATH)/bin/siad debian/sia/usr/sbin/ + cp debian/ubuntu/postinst debian/sia/DEBIAN/ + cp debian/ubuntu/preinst debian/sia/DEBIAN/ + mkdir -p debian/pkg + cd debian && dpkg-deb --build sia pkg diff --git a/provider/gcp/sia-run/README.md b/provider/gcp/sia-run/README.md new file mode 100644 index 00000000000..63638590006 --- /dev/null +++ b/provider/gcp/sia-run/README.md @@ -0,0 +1,41 @@ +# SIA for GCP Run + +## Configuration + +SIA GCP Run requires a configuration file to be present in the /etc/sia/sia_config with the +following required attributes + +```json +{ + "version": "1.0.0", + "domain": "application-domain-name", + "service": "application-service-name" +} +``` + +The Google Project administrator must create a Google Service Account with the name +``. + +SIA Configuration file provides a way to change the default user/group settings that the private key is owned by. +By default, the private key is owned by user `root` and readable by group `athenz`. If the admin wants to +provide access to their service identity private key to another user, it can be accomplished by adding the user to the group `athenz`. +If the user wants to change the user and group values, a config file must contain following optional fields: + +```json +{ + "version": "1.0.0", + "domain": "application-domain-name", + "service": "application-service-name", + "user": "unix-username", + "group": "unix-groupname" +} +``` + +SIA-RUN can be built with following parameters - +e.g. + +```shell +GOOS=linux go install -ldflags "-X main.Version=1.0.0 -X main.ZtsEndPoint=zts.athenz.io -X main.DnsDomain=gcp.athenz.io -X main.ProviderPrefix=athenz.gcp" ./... +``` + +alternatively, those parameters can be passed during runtime and runtime parameters will take precedence over build time parameters. \ No newline at end of file diff --git a/provider/gcp/sia-run/authn.go b/provider/gcp/sia-run/authn.go new file mode 100644 index 00000000000..8b66ac80659 --- /dev/null +++ b/provider/gcp/sia-run/authn.go @@ -0,0 +1,44 @@ +// +// Copyright The Athenz Authors +// +// 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 sia + +import ( + "github.com/AthenZ/athenz/libs/go/sia/host/provider" + "github.com/AthenZ/athenz/libs/go/sia/options" + "log" +) + +func GetRunConfig(configFile, metaEndpoint, region string, provider provider.Provider) (*options.Config, error) { + + config, _, err := options.InitFileConfig(configFile, metaEndpoint, false, region, "", provider) + if err != nil { + log.Printf("Unable to process configuration file '%s': %v\n", configFile, err) + log.Println("Trying to determine service details from the environment variables...") + config, _, err = options.InitEnvConfig(config, provider) + if err != nil { + log.Printf("Unable to process environment settings: %v\n", err) + // if we do not have settings in our environment, we're going + // to use fallback to retrieve values from the context ( metadata etc. ) + config, _, err = options.InitGenericProfileConfig(metaEndpoint, "", "", provider) + if err != nil { + log.Printf("Unable to determine project, domain, service etc. from context err=%v\n", err) + return nil, err + } + } + } + return config, nil +} diff --git a/provider/gcp/sia-run/authn_test.go b/provider/gcp/sia-run/authn_test.go new file mode 100644 index 00000000000..d017fb8c199 --- /dev/null +++ b/provider/gcp/sia-run/authn_test.go @@ -0,0 +1,68 @@ +// +// Copyright The Athenz Authors +// +// 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 sia + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/AthenZ/athenz/provider/gcp/sia-run/devel/metamock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setup() { + go metamock.StartMetaServer("127.0.0.1:5082") + time.Sleep(3 * time.Second) +} + +func teardown() {} + +func TestMain(m *testing.M) { + setup() + code := m.Run() + teardown() + os.Exit(code) +} + +// TestGetConfigNoConfig tests the scenario when there is no /etc/sia/sia_config, the system uses profile from meta +func TestGetConfigNoConfig(t *testing.T) { + provider := GCPRunProvider{ + Name: fmt.Sprintf("test.gcp"), + } + config, err := GetRunConfig("devel/data/sia_empty_config", "http://127.0.0.1:5082", "us-west-2", provider) + require.Nil(t, err) + require.NotNil(t, config) + assert.True(t, config.Domain == "athenz.test") + assert.True(t, config.Service == "my-sa") +} + +// TestGetConfigWithConfig test the scenario when /etc/sia/sia_config is present +func TestGetConfigWithConfig(t *testing.T) { + provider := GCPRunProvider{ + Name: fmt.Sprintf("test.gcp"), + } + config, err := GetRunConfig("devel/data/sia_config", "http://127.0.0.1:5082", "us-west-2", provider) + require.Nilf(t, err, "error should be empty, error: %v", err) + require.NotNil(t, config, "should be able to get config") + + // Make sure services are set + assert.True(t, config.Domain == "athenz") + assert.True(t, config.Service == "api") +} diff --git a/provider/gcp/sia-run/cmd/siad/main.go b/provider/gcp/sia-run/cmd/siad/main.go new file mode 100644 index 00000000000..c275fdaf158 --- /dev/null +++ b/provider/gcp/sia-run/cmd/siad/main.go @@ -0,0 +1,132 @@ +// +// Copyright The Athenz Authors +// +// 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 main + +import ( + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/AthenZ/athenz/libs/go/sia/agent" + "github.com/AthenZ/athenz/libs/go/sia/gcp/meta" + "github.com/AthenZ/athenz/libs/go/sia/options" + "github.com/AthenZ/athenz/provider/gcp/sia-run" +) + +// Following can be set by the build script using LDFLAGS + +var Version string + +const siaMainDir = "/var/lib/sia" + +func main() { + cmd := flag.String("cmd", "", "optional sub command to run") + gcpMetaEndPoint := flag.String("meta", "http://169.254.169.254:80", "meta endpoint") + ztsEndPoint := flag.String("zts", "", "Athenz Token Service (ZTS) endpoint") + ztsServerName := flag.String("ztsservername", "", "ZTS server name for tls connections (optional)") + ztsCACert := flag.String("ztscacert", "", "Athenz Token Service (ZTS) CA certificate file (optional)") + dnsDomains := flag.String("dnsdomains", "", "DNS Domains associated with the provider") + ztsPort := flag.Int("ztsport", 4443, "Athenz Token Service (ZTS) port number") + pConf := flag.String("config", "/etc/sia/sia_config", "The config file to run against") + providerPrefix := flag.String("providerprefix", "", "Provider name prefix e.g athenz.gcp") + displayVersion := flag.Bool("version", false, "Display version information") + + flag.Parse() + + if *displayVersion { + fmt.Println(Version) + os.Exit(0) + } + + log.SetFlags(log.LstdFlags) + + if *ztsEndPoint == "" { + log.Fatalln("missing zts argument") + } + ztsUrl := fmt.Sprintf("https://%s:%d/zts/v1", *ztsEndPoint, *ztsPort) + + if *dnsDomains == "" { + log.Fatalln("missing dnsdomains argument") + } + + if *providerPrefix == "" { + log.Fatalln("missing providerprefix argument") + } + + log.Printf("SIA-RUN version: %s \n", Version) + region := meta.GetRegion(*gcpMetaEndPoint) + + provider := sia.GCPRunProvider{ + Name: fmt.Sprintf("%s.%s", *providerPrefix, region), + } + + config, err := sia.GetRunConfig(*pConf, *gcpMetaEndPoint, region, provider) + if err != nil { + log.Fatalf("Unable to formulate configuration objects, error: %v\n", err) + } + + // backward compatibility sake, keeping the ConfigAccount struct + configAccount := &options.ConfigAccount{ + Name: fmt.Sprintf("%s.%s", config.Domain, config.Service), + User: config.User, + Group: config.Group, + Domain: config.Domain, + Account: config.Account, + Service: config.Service, + Zts: config.Zts, + Threshold: config.Threshold, + Roles: config.Roles, + } + + opts, err := options.NewOptions(config, configAccount, nil, siaMainDir, Version, false, region) + if err != nil { + log.Fatalf("Unable to formulate options, error: %v\n", err) + } + + instanceId, err := meta.GetInstanceId(*gcpMetaEndPoint) + if err != nil { + log.Fatalf("Unable to get instance id, error: %v\n", err) + } + + // not being able to get an instance name is not a fatal error + instanceName, err := meta.GetInstanceName(*gcpMetaEndPoint) + if err != nil { + instanceName = "" + log.Printf("Unable to get instance name, error: %v\n", err) + } + + privateIp, err := meta.GetInstancePrivateIp(*gcpMetaEndPoint) + if err != nil { + log.Fatalf("Unable to get instance private ip, error: %v\n", err) + } + + opts.MetaEndPoint = *gcpMetaEndPoint + opts.Ssh = false + opts.ZTSCACertFile = *ztsCACert + opts.ZTSServerName = *ztsServerName + opts.ZTSCloudDomains = strings.Split(*dnsDomains, ",") + opts.InstanceId = instanceId + opts.InstanceName = instanceName + opts.Provider = provider + opts.PrivateIp = privateIp + opts.SpiffeNamespace = "default" + + agent.SetupAgent(opts, siaMainDir, "") + agent.RunAgent(*cmd, ztsUrl, opts) +} diff --git a/provider/gcp/sia-run/debian/sia/DEBIAN/control b/provider/gcp/sia-run/debian/sia/DEBIAN/control new file mode 100644 index 00000000000..dc12e4ff65d --- /dev/null +++ b/provider/gcp/sia-run/debian/sia/DEBIAN/control @@ -0,0 +1,5 @@ +Package: sia-run +Version: SIA_PACKAGE_VERSION +Maintainer: cncf-athenz-maintainers@lists.cncf.io +Architecture: amd64 +Description: GCP Run Athenz Service Identity Agent diff --git a/provider/gcp/sia-run/debian/ubuntu/preinst b/provider/gcp/sia-run/debian/ubuntu/preinst new file mode 100755 index 00000000000..ef5b2515663 --- /dev/null +++ b/provider/gcp/sia-run/debian/ubuntu/preinst @@ -0,0 +1 @@ +getent group athenz >/dev/null 2>&1 || groupadd -r athenz diff --git a/provider/gcp/sia-run/devel/data/sia_config b/provider/gcp/sia-run/devel/data/sia_config new file mode 100644 index 00000000000..5f4cef13f5e --- /dev/null +++ b/provider/gcp/sia-run/devel/data/sia_config @@ -0,0 +1,15 @@ +{ + "version": "1.0.0", + "domain": "athenz", + "service": "api", + "services": { + "api": {}, + "ui": {}, + "yamas": { + "user": "nobody", + "group": "sys" + } + }, + "user": "nobody", + "access_management": true +} diff --git a/provider/gcp/sia-run/devel/data/sia_empty_config b/provider/gcp/sia-run/devel/data/sia_empty_config new file mode 100644 index 00000000000..e69de29bb2d diff --git a/provider/gcp/sia-run/devel/metamock/meta.go b/provider/gcp/sia-run/devel/metamock/meta.go new file mode 100644 index 00000000000..30e2f2ac89a --- /dev/null +++ b/provider/gcp/sia-run/devel/metamock/meta.go @@ -0,0 +1,59 @@ +// +// Copyright The Athenz Authors +// +// 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 metamock + +import ( + "io" + "log" + "net/http" +) + +var ( + identityToken = `eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InYxIn0.eyJhdWQiOiJodHRwczovL3p0cy5hdGhlbnouaW8iLCJhenAiOiIxMDIwMjM4OTY5MDQyODExMDU1NjkiLCJlbWFpbCI6Im15LXNhQG15LWdjcC1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTY3ODI1OTEzMSwiaWF0IjoxNjc4MjU1NTMxLCJpc3MiOiJodHRwczovL2drZS1tZXRhLW1vY2siLCJzdWIiOiIxMDIwMjM4OTY5MDQyODExMDU1NjkifQ.sRQdLhNm8WvYQynpshGRtgcngj0XERF3PjywyXfNP_0ivP6nszQvMZIqp9_SysfeYX7VrPPil4OGVfvEbkiyEQ` + domain = `athenz.test` + projectId = `my-gcp-project` + sa = `my-sa@my-gcp-project.iam.gserviceaccount.com` + instanceId = `3692465022399257023` + instanceName = `my-vm` +) + +func StartMetaServer(EndPoint string) { + http.HandleFunc("/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://zts.athenz.io", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, identityToken) + }) + http.HandleFunc("/computeMetadata/v1/project/attributes/athenz-domain", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, domain) + }) + http.HandleFunc("/computeMetadata/v1/project/project-id", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, projectId) + }) + http.HandleFunc("/computeMetadata/v1/instance/service-accounts/default/email", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, sa) + }) + http.HandleFunc("/computeMetadata/v1/instance/id", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, instanceId) + }) + http.HandleFunc("/computeMetadata/v1/instance/name", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, instanceName) + }) + + log.Println("Starting GCP Run Meta Mock listening on: " + EndPoint) + err := http.ListenAndServe(EndPoint, nil) + if err != nil { + log.Fatalf("ListenAndServe: %v\n", err) + } +} diff --git a/provider/gcp/sia-run/pom.xml b/provider/gcp/sia-run/pom.xml new file mode 100644 index 00000000000..b311f1714f8 --- /dev/null +++ b/provider/gcp/sia-run/pom.xml @@ -0,0 +1,71 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.11.64-SNAPSHOT + ../../../pom.xml + + + sia-gcp-run + jar + sia-gcp-run + Service Identity Agent for GCP Run + + + true + true + + + + + + org.codehaus.mojo + exec-maven-plugin + ${maven-exec-plugin.version} + + + + exec + + compile + + + + make + + clean + all + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + default-jar + + + + + + + diff --git a/provider/gcp/sia-run/provider.go b/provider/gcp/sia-run/provider.go new file mode 100644 index 00000000000..77d5a7d3924 --- /dev/null +++ b/provider/gcp/sia-run/provider.go @@ -0,0 +1,113 @@ +// +// Copyright The Athenz Authors +// +// 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 sia + +import ( + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + gcpa "github.com/AthenZ/athenz/libs/go/sia/gcp/attestation" + "github.com/AthenZ/athenz/libs/go/sia/gcp/meta" + "github.com/AthenZ/athenz/libs/go/sia/host/ip" + "github.com/AthenZ/athenz/libs/go/sia/host/signature" + "github.com/AthenZ/athenz/libs/go/sia/host/utils" + "net" + "net/url" +) + +type GCPRunProvider struct { + Name string +} + +// GetName returns the name of the current provider +func (gcprun GCPRunProvider) GetName() string { + return gcprun.Name +} + +// GetHostname returns the hostname as per the provider +func (gcprun GCPRunProvider) GetHostname(fqdn bool) string { + return utils.GetHostname(fqdn) +} + +func (gcprun GCPRunProvider) AttestationData(_ string, _ crypto.PrivateKey, _ *signature.SignatureInfo) (string, error) { + result, err := meta.GetData("http://169.254.169.254", "/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://zts.athenz.io&format=full") + if err == nil { + return string(result), nil + } + return "", fmt.Errorf("error while retriveing attestation data") +} + +func (gcprun GCPRunProvider) PrepareKey(_ string) (crypto.PrivateKey, error) { + return "", fmt.Errorf("not implemented") +} + +func (gcprun GCPRunProvider) GetCsrDn() pkix.Name { + return pkix.Name{} +} + +func (gcprun GCPRunProvider) GetSanDns(_ string, _ bool, _ bool, _ []string) []string { + return nil +} + +func (gcprun GCPRunProvider) GetSanUri(_ string, _ ip.Opts, _, _ string) []*url.URL { + return nil +} + +func (gcprun GCPRunProvider) GetEmail(_ string) []string { + return nil +} + +func (gcprun GCPRunProvider) GetRoleDnsNames(_ *x509.Certificate, _ string) []string { + return nil +} + +func (gcprun GCPRunProvider) GetSanIp(_ map[string]bool, _ []net.IP, _ ip.Opts) []net.IP { + return nil +} + +func (gcprun GCPRunProvider) GetSuffixes() []string { + return []string{} +} + +func (gcprun GCPRunProvider) CloudAttestationData(base, svc, ztsServerName string) (string, error) { + return gcpa.New(base, svc, ztsServerName) +} + +func (gcprun GCPRunProvider) GetAccountDomainServiceFromMeta(base string) (string, string, string, error) { + account, err := meta.GetProject(base) + if err != nil { + return "", "", "", err + } + domain, err := meta.GetDomain(base) + if err != nil { + return account, "", "", err + } + service, err := meta.GetService(base) + if err != nil { + return account, domain, "", err + } + return account, domain, service, nil +} + +func (gcprun GCPRunProvider) GetAccessManagementProfileFromMeta(base string) (string, error) { + return "", fmt.Errorf("not implemented") +} + +func (gcprun GCPRunProvider) GetAdditionalSshHostPrincipals(base string) (string, error) { + return "", nil +} diff --git a/provider/gcp/sia-run/sia-run.spec b/provider/gcp/sia-run/sia-run.spec new file mode 100644 index 00000000000..de06e236258 --- /dev/null +++ b/provider/gcp/sia-run/sia-run.spec @@ -0,0 +1,27 @@ +Name: sia-run +Version: %{PACKAGE_VERSION} +Release: %{RELEASE}.el%{CENTOS_VERSION} +Summary: Athenz Service Identity Agent (SIA) for GCP Run + +Group: System Environment/Daemons +License: Apache 2.0 +URL: https://www.athenz.io/ +BuildRoot: %{SOURCEURL0}/rpm/BUILD/%{name}-%{version}-%{release} + +%{?systemd_ordering} + +%description +%{summary} + +%build + +%pre +getent group athenz >/dev/null 2>&1 || groupadd -g 10952 -r athenz + +%install +mkdir -p $RPM_BUILD_ROOT/%{_sbindir} +mkdir -p $RPM_BUILD_ROOT/etc/sia/ +install -pm 0755 %{BIN_DIR}/siad $RPM_BUILD_ROOT/%{_sbindir} + +%files +%{_sbindir}/siad