Skip to content

Commit

Permalink
SIA (Service Identity Agent for GCP Runs (#2693)
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Avetisyan <[email protected]>
  • Loading branch information
havetisyan authored Aug 19, 2024
1 parent ebb1acc commit c3d2433
Show file tree
Hide file tree
Showing 16 changed files with 743 additions and 20 deletions.
2 changes: 1 addition & 1 deletion libs/go/sia/options/mockgcpprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
50 changes: 32 additions & 18 deletions libs/go/sia/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"errors"
"fmt"
legacy "github.com/AthenZ/athenz/libs/go/sia/aws/options"

"log"
"os"
"strings"
Expand Down Expand Up @@ -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")
Expand All @@ -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,
Expand All @@ -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) {
Expand Down
76 changes: 75 additions & 1 deletion libs/go/sia/options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
73 changes: 73 additions & 0 deletions provider/gcp/sia-run/Makefile
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions provider/gcp/sia-run/README.md
Original file line number Diff line number Diff line change
@@ -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
`<application-service-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.
44 changes: 44 additions & 0 deletions provider/gcp/sia-run/authn.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit c3d2433

Please sign in to comment.