Skip to content

Commit

Permalink
Support all available Yandex Cloud API auth methods (#1263)
Browse files Browse the repository at this point in the history
* Support all available Yandex Cloud API auth methods

* Fix lint
  • Loading branch information
GennadySpb authored Mar 17, 2022
1 parent 5f2d80e commit e74b99e
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 13 deletions.
4 changes: 1 addition & 3 deletions providers/yandex/compute_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ func (g *DiskGenerator) loadDisks(sdk *ycsdk.SDK, folderID string) ([]*compute.D
}

func (g *DiskGenerator) InitResources() error {
sdk, err := ycsdk.Build(context.Background(), ycsdk.Config{
Credentials: ycsdk.OAuthToken(g.Args["token"].(string)),
})
sdk, err := g.InitSDK()
if err != nil {
return err
}
Expand Down
4 changes: 1 addition & 3 deletions providers/yandex/compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ func (g *InstanceGenerator) loadInstances(sdk *ycsdk.SDK, folderID string) ([]*c
}

func (g *InstanceGenerator) InitResources() error {
sdk, err := ycsdk.Build(context.Background(), ycsdk.Config{
Credentials: ycsdk.OAuthToken(g.Args["token"].(string)),
})
sdk, err := g.InitSDK()
if err != nil {
return err
}
Expand Down
23 changes: 16 additions & 7 deletions providers/yandex/yandex_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,25 @@ import (
"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

const KeyToken = "token"
const KeyFolderID = "folder_id"
const KeySaKeyFileOrContent = "sa_key_or_content"

type YandexProvider struct { //nolint
terraformutils.Provider
oauthToken string
folderID string
token string
saKeyFileOrContent string
folderID string
}

func (p *YandexProvider) Init(args []string) error {
if os.Getenv("YC_TOKEN") == "" {
return errors.New("set YC_TOKEN env var")
if ycToken, ok := os.LookupEnv("YC_TOKEN"); ok {
p.token = ycToken
}

if saKeyFileOrContent, ok := os.LookupEnv("YC_SERVICE_ACCOUNT_KEY_FILE"); ok {
p.saKeyFileOrContent = saKeyFileOrContent
}
p.oauthToken = os.Getenv("YC_TOKEN")

if len(args) > 0 {
// first args is target folder ID
Expand Down Expand Up @@ -77,8 +85,9 @@ func (p *YandexProvider) InitService(serviceName string, verbose bool) error {
p.Service.SetVerbose(verbose)
p.Service.SetProviderName(p.GetName())
p.Service.SetArgs(map[string]interface{}{
"folder_id": p.folderID,
"token": p.oauthToken,
KeyFolderID: p.folderID,
KeyToken: p.token,
KeySaKeyFileOrContent: p.saKeyFileOrContent,
})
return nil
}
94 changes: 94 additions & 0 deletions providers/yandex/yandex_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,103 @@
package yandex

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"strings"
"time"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/mitchellh/go-homedir"
ycsdk "github.com/yandex-cloud/go-sdk"
"github.com/yandex-cloud/go-sdk/iamkey"
)

type YandexService struct { //nolint
terraformutils.Service
}

func (y *YandexService) InitSDK() (*ycsdk.SDK, error) {
if saKeyOrContent := y.Args[KeySaKeyFileOrContent].(string); saKeyOrContent != "" {
contents, _, err := pathOrContents(saKeyOrContent)
if err != nil {
return nil, fmt.Errorf("Error loading credentials: %s", err)
}

key, err := iamKeyFromJSONContent(contents)
if err != nil {
return nil, err
}
serviceAccountKey, err := ycsdk.ServiceAccountKey(key)
if err != nil {
return nil, err
}
return ycsdk.Build(context.Background(), ycsdk.Config{
Credentials: serviceAccountKey},
)
}

if cToken := y.Args[KeyToken].(string); cToken != "" {
if strings.HasPrefix(cToken, "t1.") && strings.Count(cToken, ".") == 2 {
return ycsdk.Build(context.Background(), ycsdk.Config{
Credentials: ycsdk.NewIAMTokenCredentials(cToken)},
)
}
return ycsdk.Build(context.Background(), ycsdk.Config{
Credentials: ycsdk.OAuthToken(cToken),
})
}

if sa := ycsdk.InstanceServiceAccount(); checkServiceAccountAvailable(context.Background(), sa) {
return ycsdk.Build(context.Background(), ycsdk.Config{
Credentials: sa,
})
}

return nil, fmt.Errorf("one of 'YC_TOKEN' or 'YC_SERVICE_ACCOUNT_KEY_FILE' env variable should be specified; if you are inside compute instance, you can attach service account to it in order to authenticate via instance service account")
}

func pathOrContents(poc string) (string, bool, error) {
if len(poc) == 0 {
return poc, false, nil
}

path := poc
if path[0] == '~' {
var err error
path, err = homedir.Expand(path)
if err != nil {
return path, true, err
}
}

if _, err := os.Stat(path); err == nil {
contents, err := ioutil.ReadFile(path)
return string(contents), true, err
}

return poc, false, nil
}

func iamKeyFromJSONContent(content string) (*iamkey.Key, error) {
key := &iamkey.Key{}
err := json.Unmarshal([]byte(content), key)
if err != nil {
return nil, fmt.Errorf("service account JSON key unmarshal fail: %s", err)
}
return key, nil
}

func checkServiceAccountAvailable(ctx context.Context, sa ycsdk.NonExchangeableCredentials) bool {
dialer := net.Dialer{Timeout: 50 * time.Millisecond}
conn, err := dialer.Dial("tcp", net.JoinHostPort(ycsdk.InstanceMetadataAddr, "80"))
if err != nil {
return false
}
_ = conn.Close()
_, err = sa.IAMToken(ctx)
return err == nil
}

0 comments on commit e74b99e

Please sign in to comment.