mirror of
https://github.com/foomo/posh-providers.git
synced 2025-10-16 12:35:41 +00:00
fix(azure/az): support service principals
This commit is contained in:
parent
88d82327fb
commit
0c3397ceae
@ -2,6 +2,8 @@ package az
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/foomo/posh/pkg/log"
|
"github.com/foomo/posh/pkg/log"
|
||||||
@ -11,11 +13,21 @@ import (
|
|||||||
|
|
||||||
func AuthChecker(ctx context.Context, l log.Logger) []check.Info {
|
func AuthChecker(ctx context.Context, l log.Logger) []check.Info {
|
||||||
name := "Azure"
|
name := "Azure"
|
||||||
out, err := shell.New(ctx, l, "az", "account", "list", "--output", "none").Quiet().CombinedOutput()
|
out, err := shell.New(ctx, l, "az", "account", "list", "--output", "json").Quiet().CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []check.Info{check.NewFailureInfo(name, "Error: "+err.Error())}
|
return []check.Info{check.NewFailureInfo(name, "Error: "+err.Error())}
|
||||||
} else if strings.Contains(string(out), "az login") {
|
} else if strings.Contains(string(out), "az login") {
|
||||||
return []check.Info{check.NewNoteInfo(name, "Unauthenticated")}
|
return []check.Info{check.NewNoteInfo(name, "Unauthenticated")}
|
||||||
}
|
}
|
||||||
return []check.Info{check.NewSuccessInfo(name, "Authenticated")}
|
|
||||||
|
var res []map[string]any
|
||||||
|
note := "Authenticated"
|
||||||
|
if err := json.Unmarshal(out, &res); err == nil {
|
||||||
|
if len(res) > 0 && res[0]["user"] != nil {
|
||||||
|
if user, ok := res[0]["user"].(map[string]any); ok {
|
||||||
|
note += fmt.Sprintf(" as %s: %s", user["type"], user["name"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []check.Info{check.NewSuccessInfo(name, note)}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,14 @@ func NewCommand(l log.Logger, az *AZ, kubectl *kubectl.Kubectl, opts ...CommandO
|
|||||||
{
|
{
|
||||||
Name: "login",
|
Name: "login",
|
||||||
Description: "Log in to Azure",
|
Description: "Log in to Azure",
|
||||||
Execute: inst.login,
|
Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error {
|
||||||
|
fs.Internal().String("service-principal", "", "Service principal to use for authentication")
|
||||||
|
if err := fs.Internal().SetValues("service-principal", inst.az.Config().ServicePrincipalNames()...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Execute: inst.login,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "logout",
|
Name: "logout",
|
||||||
@ -240,10 +247,34 @@ func (c *Command) exec(ctx context.Context, r *readline.Readline) error {
|
|||||||
|
|
||||||
func (c *Command) login(ctx context.Context, r *readline.Readline) error {
|
func (c *Command) login(ctx context.Context, r *readline.Readline) error {
|
||||||
fs := r.FlagSets().Default()
|
fs := r.FlagSets().Default()
|
||||||
return shell.New(ctx, c.l, "az", "login",
|
ifs := r.FlagSets().Internal()
|
||||||
"--allow-no-subscriptions",
|
|
||||||
"--tenant", c.az.Config().TenantID,
|
servicePricipal, err := ifs.GetString("service-principal")
|
||||||
).
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
if servicePricipal != "" {
|
||||||
|
sp, err := c.az.cfg.ServicePrincipal(servicePricipal)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
args = append(args,
|
||||||
|
"--service-principal",
|
||||||
|
"--username", sp.ClientID,
|
||||||
|
"--password", sp.ClientSecret,
|
||||||
|
"--tenant", sp.TenantID,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
args = append(args,
|
||||||
|
"--allow-no-subscriptions",
|
||||||
|
"--tenant", c.az.Config().TenantID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return shell.New(ctx, c.l, "az", "login").
|
||||||
|
Args(args...).
|
||||||
Args(fs.Visited().Args()...).
|
Args(fs.Visited().Args()...).
|
||||||
Args(r.AdditionalArgs()...).
|
Args(r.AdditionalArgs()...).
|
||||||
Args(r.AdditionalFlags()...).
|
Args(r.AdditionalFlags()...).
|
||||||
|
|||||||
@ -14,6 +14,17 @@ type Config struct {
|
|||||||
TenantID string `json:"tenantId" yaml:"tenantId"`
|
TenantID string `json:"tenantId" yaml:"tenantId"`
|
||||||
// Subscription configurations
|
// Subscription configurations
|
||||||
Subscriptions map[string]Subscription `json:"subscriptions" yaml:"subscriptions"`
|
Subscriptions map[string]Subscription `json:"subscriptions" yaml:"subscriptions"`
|
||||||
|
// Authentication service principals
|
||||||
|
ServicePrincipals map[string]ServicePrincipal `json:"servicePrincipals" yaml:"servicePrincipals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServicePrincipal struct {
|
||||||
|
// Tenant id
|
||||||
|
TenantID string `json:"tenantId" yaml:"tenantId"`
|
||||||
|
// Application client id
|
||||||
|
ClientID string `json:"clientId" yaml:"clientId"`
|
||||||
|
// Application password
|
||||||
|
ClientSecret string `json:"clientSecret" yaml:"clientSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) Subscription(name string) (Subscription, error) {
|
func (c Config) Subscription(name string) (Subscription, error) {
|
||||||
@ -29,3 +40,17 @@ func (c Config) SubscriptionNames() []string {
|
|||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Config) ServicePrincipal(name string) (ServicePrincipal, error) {
|
||||||
|
value, ok := c.ServicePrincipals[name]
|
||||||
|
if !ok {
|
||||||
|
return ServicePrincipal{}, errors.Errorf("service principal not found: %s", name)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) ServicePrincipalNames() []string {
|
||||||
|
keys := lo.Keys(c.ServicePrincipals)
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
# yaml-language-server: $schema=config.schema.json
|
# yaml-language-server: $schema=config.schema.json
|
||||||
configPath: .posh/config/azure
|
configPath: .posh/config/azure
|
||||||
tenantId: xxxx-xx-xx-xx-xxxx
|
tenantId: xxxx-xx-xx-xx-xxxx
|
||||||
|
servicePrincipals:
|
||||||
|
dev:
|
||||||
|
tenantId: xxxx-xx-xx-xx-xxxx
|
||||||
|
clientId: xxxx-xx-xx-xx-xxxx
|
||||||
|
clientSecret: xxxx-xx-xx-xx-xxxx
|
||||||
subscriptions:
|
subscriptions:
|
||||||
development:
|
development:
|
||||||
name: xxxx-xx-xx-xx-xxxx
|
name: xxxx-xx-xx-xx-xxxx
|
||||||
|
|||||||
@ -104,7 +104,7 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||||
be, err := inst.cfg.Backend(r.Args().At(0))
|
backend, err := inst.cfg.Backend(r.Args().At(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -123,11 +123,11 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
|
|||||||
storageArgs = strings.Trim(strings.Trim(storageArgs, "\""), "'")
|
storageArgs = strings.Trim(strings.Trim(storageArgs, "\""), "'")
|
||||||
|
|
||||||
// Create a new resource group
|
// Create a new resource group
|
||||||
inst.l.Info("creating resource group:", be.ResourceGroup)
|
inst.l.Info("creating resource group:", backend.ResourceGroup)
|
||||||
if err := shell.New(ctx, inst.l, "az", "group", "create").
|
if err := shell.New(ctx, inst.l, "az", "group", "create").
|
||||||
Args("--resource-group", be.ResourceGroup).
|
Args("--resource-group", backend.ResourceGroup).
|
||||||
Args("--subscription", be.Subscription).
|
Args("--subscription", backend.Subscription).
|
||||||
Args("--location", be.Location).
|
Args("--location", backend.Location).
|
||||||
Args(strings.Split(groupArgs, " ")...).
|
Args(strings.Split(groupArgs, " ")...).
|
||||||
Args(r.FlagSets().Default().Visited().Args()...).
|
Args(r.FlagSets().Default().Visited().Args()...).
|
||||||
Run(); err != nil {
|
Run(); err != nil {
|
||||||
@ -135,12 +135,12 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new resource group
|
// Create a new resource group
|
||||||
inst.l.Info("creating storage account:", be.StorageAccount)
|
inst.l.Info("creating storage account:", backend.StorageAccount)
|
||||||
if err := shell.New(ctx, inst.l, "az", "storage", "account", "create").
|
if err := shell.New(ctx, inst.l, "az", "storage", "account", "create").
|
||||||
Args("--name", be.StorageAccount).
|
Args("--name", backend.StorageAccount).
|
||||||
Args("--resource-group", be.ResourceGroup).
|
Args("--resource-group", backend.ResourceGroup).
|
||||||
Args("--subscription", be.Subscription).
|
Args("--subscription", backend.Subscription).
|
||||||
Args("--location", be.Location).
|
Args("--location", backend.Location).
|
||||||
Args(strings.Split(storageArgs, " ")...).
|
Args(strings.Split(storageArgs, " ")...).
|
||||||
Args(r.FlagSets().Default().Visited().Args()...).
|
Args(r.FlagSets().Default().Visited().Args()...).
|
||||||
Run(); err != nil {
|
Run(); err != nil {
|
||||||
@ -148,23 +148,16 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retrieve storage key
|
// retrieve storage key
|
||||||
inst.l.Info("retrieving storage key")
|
storageAccountKey, err := inst.getStorageAccountKey(ctx, backend)
|
||||||
sk, err := shell.New(ctx, inst.l, "az", "storage", "account", "keys", "list").
|
|
||||||
Args("--resource-group", be.ResourceGroup).
|
|
||||||
Args("--subscription", be.Subscription).
|
|
||||||
Args("--account-name", be.StorageAccount).
|
|
||||||
Args("-o", "tsv", "--query", "'[0].value'").
|
|
||||||
Output()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sks := strings.ReplaceAll(strings.TrimSpace(string(sk)), "\n", "")
|
|
||||||
|
|
||||||
inst.l.Info("creating storage container:", be.Container)
|
inst.l.Info("creating storage container:", backend.Container)
|
||||||
return shell.New(ctx, inst.l, "az", "storage", "container", "create").
|
return shell.New(ctx, inst.l, "az", "storage", "container", "create").
|
||||||
Args("--account-name", be.StorageAccount).
|
Args("--account-name", backend.StorageAccount).
|
||||||
Args("--account-key", sks).
|
Args("--account-key", storageAccountKey).
|
||||||
Args("--name", be.Container).
|
Args("--name", backend.Container).
|
||||||
Run()
|
Run()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -172,15 +165,21 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
|
|||||||
Name: "login",
|
Name: "login",
|
||||||
Description: "Log into your object storage backend",
|
Description: "Log into your object storage backend",
|
||||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||||
be, sks, err := inst.backendKey(ctx, r.Args().At(0))
|
backend, err := inst.cfg.Backend(r.Args().At(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return shell.New(ctx, inst.l, "pulumi", "login", fmt.Sprintf("azblob://%s", be.Container)).
|
storageAccountKey, err := inst.getStorageAccountKey(ctx, backend)
|
||||||
Env("AZURE_STORAGE_ACCOUNT=" + be.StorageAccount).
|
if err != nil {
|
||||||
Env("AZURE_STORAGE_KEY=" + sks).
|
return err
|
||||||
Env("ARM_SUBSCRIPTION_ID=" + be.Subscription).
|
}
|
||||||
|
|
||||||
|
return shell.New(ctx, inst.l, "pulumi", "login", fmt.Sprintf("azblob://%s", backend.Container)).
|
||||||
|
Env("AZURE_STORAGE_ACCOUNT=" + backend.StorageAccount).
|
||||||
|
Env("AZURE_STORAGE_KEY=" + storageAccountKey).
|
||||||
|
Env("ARM_SUBSCRIPTION_ID=" + backend.Subscription).
|
||||||
|
Debug().
|
||||||
Run()
|
Run()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -503,7 +502,12 @@ func (c *Command) executeStack(ctx context.Context, r *readline.Readline) error
|
|||||||
proj := r.Args().At(2)
|
proj := r.Args().At(2)
|
||||||
stack := r.Args().At(3)
|
stack := r.Args().At(3)
|
||||||
|
|
||||||
be, storageAccountKey, err := c.backendKey(ctx, e)
|
be, err := c.cfg.Backend(r.Args().At(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
storageAccountKey, err := c.getStorageAccountKey(ctx, be)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -551,12 +555,7 @@ func (c *Command) completeProjects(ctx context.Context, t tree.Root, r *readline
|
|||||||
}).([]goprompt.Suggest)
|
}).([]goprompt.Suggest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) backendKey(ctx context.Context, env string) (Backend, string, error) {
|
func (c *Command) getStorageAccountKey(ctx context.Context, be Backend) (string, error) {
|
||||||
be, err := c.cfg.Backend(env)
|
|
||||||
if err != nil {
|
|
||||||
return Backend{}, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve storage key
|
// retrieve storage key
|
||||||
c.l.Info("retrieving storage key")
|
c.l.Info("retrieving storage key")
|
||||||
sk, err := shell.New(ctx, c.l, "az", "storage", "account", "keys", "list").
|
sk, err := shell.New(ctx, c.l, "az", "storage", "account", "keys", "list").
|
||||||
@ -566,10 +565,10 @@ func (c *Command) backendKey(ctx context.Context, env string) (Backend, string,
|
|||||||
Args("-o", "tsv", "--query", "'[0].value'").
|
Args("-o", "tsv", "--query", "'[0].value'").
|
||||||
Output()
|
Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Backend{}, "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return be, strings.ReplaceAll(strings.TrimSpace(string(sk)), "\n", ""), nil
|
return strings.ReplaceAll(strings.TrimSpace(string(sk)), "\n", ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) completeStacks(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
|
func (c *Command) completeStacks(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user