fix(azure/az): support service principals

This commit is contained in:
Kevin Franklin Kim 2025-08-07 16:43:34 +02:00
parent 88d82327fb
commit 0c3397ceae
No known key found for this signature in database
5 changed files with 115 additions and 43 deletions

View File

@ -2,6 +2,8 @@ package az
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/foomo/posh/pkg/log"
@ -11,11 +13,21 @@ import (
func AuthChecker(ctx context.Context, l log.Logger) []check.Info {
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 {
return []check.Info{check.NewFailureInfo(name, "Error: "+err.Error())}
} else if strings.Contains(string(out), "az login") {
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)}
}

View File

@ -69,7 +69,14 @@ func NewCommand(l log.Logger, az *AZ, kubectl *kubectl.Kubectl, opts ...CommandO
{
Name: "login",
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",
@ -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 {
fs := r.FlagSets().Default()
return shell.New(ctx, c.l, "az", "login",
"--allow-no-subscriptions",
"--tenant", c.az.Config().TenantID,
).
ifs := r.FlagSets().Internal()
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(r.AdditionalArgs()...).
Args(r.AdditionalFlags()...).

View File

@ -14,6 +14,17 @@ type Config struct {
TenantID string `json:"tenantId" yaml:"tenantId"`
// Subscription configurations
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) {
@ -29,3 +40,17 @@ func (c Config) SubscriptionNames() []string {
sort.Strings(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
}

View File

@ -1,6 +1,11 @@
# yaml-language-server: $schema=config.schema.json
configPath: .posh/config/azure
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:
development:
name: xxxx-xx-xx-xx-xxxx

View File

@ -104,7 +104,7 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
return nil
},
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 {
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, "\""), "'")
// 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").
Args("--resource-group", be.ResourceGroup).
Args("--subscription", be.Subscription).
Args("--location", be.Location).
Args("--resource-group", backend.ResourceGroup).
Args("--subscription", backend.Subscription).
Args("--location", backend.Location).
Args(strings.Split(groupArgs, " ")...).
Args(r.FlagSets().Default().Visited().Args()...).
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
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").
Args("--name", be.StorageAccount).
Args("--resource-group", be.ResourceGroup).
Args("--subscription", be.Subscription).
Args("--location", be.Location).
Args("--name", backend.StorageAccount).
Args("--resource-group", backend.ResourceGroup).
Args("--subscription", backend.Subscription).
Args("--location", backend.Location).
Args(strings.Split(storageArgs, " ")...).
Args(r.FlagSets().Default().Visited().Args()...).
Run(); err != nil {
@ -148,23 +148,16 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
}
// retrieve storage key
inst.l.Info("retrieving storage key")
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()
storageAccountKey, err := inst.getStorageAccountKey(ctx, backend)
if err != nil {
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").
Args("--account-name", be.StorageAccount).
Args("--account-key", sks).
Args("--name", be.Container).
Args("--account-name", backend.StorageAccount).
Args("--account-key", storageAccountKey).
Args("--name", backend.Container).
Run()
},
},
@ -172,15 +165,21 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
Name: "login",
Description: "Log into your object storage backend",
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 {
return err
}
return shell.New(ctx, inst.l, "pulumi", "login", fmt.Sprintf("azblob://%s", be.Container)).
Env("AZURE_STORAGE_ACCOUNT=" + be.StorageAccount).
Env("AZURE_STORAGE_KEY=" + sks).
Env("ARM_SUBSCRIPTION_ID=" + be.Subscription).
storageAccountKey, err := inst.getStorageAccountKey(ctx, backend)
if err != nil {
return err
}
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()
},
},
@ -503,7 +502,12 @@ func (c *Command) executeStack(ctx context.Context, r *readline.Readline) error
proj := r.Args().At(2)
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 {
return err
}
@ -551,12 +555,7 @@ func (c *Command) completeProjects(ctx context.Context, t tree.Root, r *readline
}).([]goprompt.Suggest)
}
func (c *Command) backendKey(ctx context.Context, env string) (Backend, string, error) {
be, err := c.cfg.Backend(env)
if err != nil {
return Backend{}, "", err
}
func (c *Command) getStorageAccountKey(ctx context.Context, be Backend) (string, error) {
// retrieve storage key
c.l.Info("retrieving storage key")
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'").
Output()
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 {