Skip to content

Commit

Permalink
Add login command
Browse files Browse the repository at this point in the history
1. Check for the existence of `~/.azure`, which signals we're already
   logged in using some means.
2. Check the environment for a service principal as defined by the
   environment variables `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and
   either `AZURE_CLIENT_SECRET` or `AZURE_CLIENT_CERTIFICATE_PATH` and
   (optionally) `AZURE_CLIENT_CERTIFICATE_PASSWORD`.
3. Check the `AZURE_CLIENT_ID` environment variable for the client ID of
   a user-assigned managed identity.
4. Use the system-assigned managed identity for the Azure resource if
   it's enabled.

Signed-off-by: Leo Bergnéhr <leo@bergnehr.se>
  • Loading branch information
lbergnehr-sectra authored and lbergnehr committed Dec 17, 2024
1 parent bc4d3dd commit 95b58da
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ bin
.cnab
/build/git_askpass.sh
az
!pkg/az
!.gitignore
2 changes: 2 additions & 0 deletions pkg/az/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ func (s *TypedStep) UnmarshalYAML(unmarshal func(interface{}) error) error {
continue
case "group":
cmd = &GroupCommand{}
case "login":
cmd = &LoginCommand{}
default: // It's a custom user command
customCmd := &UserCommand{}
b, err := yaml.Marshal(step)
Expand Down
67 changes: 67 additions & 0 deletions pkg/az/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package az

import (
"context"
"os"
"path/filepath"

"get.porter.sh/porter/pkg/exec/builder"
)

var (
_ TypedCommand = &LoginCommand{}
_ builder.HasErrorHandling = &LoginCommand{}
)

// LoginCommand handles logging into Azure
type LoginCommand struct {
action string
Description string `yaml:"description"`
}

func (c *LoginCommand) HandleError(ctx context.Context, err builder.ExitError, stdout string, stderr string) error {
// Handle specific login errors if necessary
return err
}

func (c *LoginCommand) GetWorkingDir() string {
return ""
}

func (c *LoginCommand) SetAction(action string) {
c.action = action
}

func (c *LoginCommand) GetCommand() string {
return "az"
}

func (c *LoginCommand) GetArguments() []string {
if _, err := os.Stat(filepath.Join(os.Getenv("HOME"), ".azure")); err == nil {
return []string{}
}
return []string{"login"}
}

func (c *LoginCommand) GetFlags() builder.Flags {
flags := builder.Flags{}

if os.Getenv("AZURE_CLIENT_ID") != "" && os.Getenv("AZURE_CLIENT_SECRET") != "" && os.Getenv("AZURE_TENANT_ID") != "" {
// Add flags for service principal authentication
flags = append(flags, builder.NewFlag("username", os.Getenv("AZURE_CLIENT_ID")))
flags = append(flags, builder.NewFlag("password", os.Getenv("AZURE_CLIENT_SECRET")))
flags = append(flags, builder.NewFlag("tenant", os.Getenv("AZURE_TENANT_ID")))
} else if os.Getenv("AZURE_CLIENT_ID") != "" {
// Add flag for user-assigned managed identity
flags = append(flags, builder.NewFlag("username", os.Getenv("AZURE_CLIENT_ID")))
} else {
// Add flag for system-assigned managed identity
flags = append(flags, builder.NewFlag("identity", ""))
}

return flags
}

func (c *LoginCommand) SuppressesOutput() bool {
return false
}
104 changes: 104 additions & 0 deletions pkg/az/login_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package az

import (
"os"
"path/filepath"
"testing"

"get.porter.sh/porter/pkg/exec/builder"
"github.com/stretchr/testify/assert"
)

func TestLoginCommand_GetArguments_ServicePrincipal(t *testing.T) {
tempHome := t.TempDir()
os.Setenv("HOME", tempHome)
os.Setenv("AZURE_CLIENT_ID", "test-client-id")
os.Setenv("AZURE_CLIENT_SECRET", "test-client-secret")
os.Setenv("AZURE_TENANT_ID", "test-tenant-id")
defer os.Unsetenv("AZURE_CLIENT_ID")
defer os.Unsetenv("AZURE_CLIENT_SECRET")
defer os.Unsetenv("AZURE_TENANT_ID")

cmd := &LoginCommand{}
args := cmd.GetArguments()

expectedArgs := []string{"login"}
assert.Equal(t, expectedArgs, args)
}

func TestLoginCommand_GetArguments_ExistingAzureDirectory(t *testing.T) {
tempHome := t.TempDir()
os.Setenv("HOME", tempHome)
homeDir := os.Getenv("HOME")
os.MkdirAll(filepath.Join(homeDir, ".azure"), 0755)
defer os.RemoveAll(filepath.Join(homeDir, ".azure"))

cmd := &LoginCommand{}
args := cmd.GetArguments()

expectedArgs := []string{}
assert.Equal(t, expectedArgs, args)
}

func TestLoginCommand_GetArguments_ManagedIdentity(t *testing.T) {
tempHome := t.TempDir()
os.Setenv("HOME", tempHome)
os.Unsetenv("AZURE_CLIENT_ID")
os.Unsetenv("AZURE_CLIENT_SECRET")
os.Unsetenv("AZURE_TENANT_ID")

cmd := &LoginCommand{}
args := cmd.GetArguments()

expectedArgs := []string{"login"}
assert.Equal(t, expectedArgs, args)
}

func TestLoginCommand_GetFlags_ServicePrincipal(t *testing.T) {
tempHome := t.TempDir()
os.Setenv("HOME", tempHome)
os.Setenv("AZURE_CLIENT_ID", "test-client-id")
os.Setenv("AZURE_CLIENT_SECRET", "test-client-secret")
os.Setenv("AZURE_TENANT_ID", "test-tenant-id")
defer os.Unsetenv("AZURE_CLIENT_ID")
defer os.Unsetenv("AZURE_CLIENT_SECRET")
defer os.Unsetenv("AZURE_TENANT_ID")

cmd := &LoginCommand{}
flags := cmd.GetFlags()

expectedFlags := builder.Flags{
builder.NewFlag("username", "test-client-id"),
builder.NewFlag("password", "test-client-secret"),
builder.NewFlag("tenant", "test-tenant-id"),
}
assert.Equal(t, expectedFlags, flags)
}

func TestLoginCommand_GetFlags_UserAssignedManagedIdentity(t *testing.T) {
tempHome := t.TempDir()
os.Setenv("HOME", tempHome)
os.Setenv("AZURE_CLIENT_ID", "test-client-id")
defer os.Unsetenv("AZURE_CLIENT_ID")

cmd := &LoginCommand{}
flags := cmd.GetFlags()

expectedFlags := builder.Flags{
builder.NewFlag("username", "test-client-id"),
}
assert.Equal(t, expectedFlags, flags)
}

func TestLoginCommand_GetFlags_SystemManagedIdentity(t *testing.T) {
tempHome := t.TempDir()
os.Setenv("HOME", tempHome)

cmd := &LoginCommand{}
flags := cmd.GetFlags()

expectedFlags := builder.Flags{
builder.NewFlag("identity", ""),
}
assert.Equal(t, expectedFlags, flags)
}
10 changes: 8 additions & 2 deletions pkg/az/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
}
},
"installBicep": {
"description": "Indicates if Bicep should be install",
"description": "Indicates if Bicep should be installed",
"type": "boolean"
}
},
Expand Down Expand Up @@ -174,7 +174,8 @@
},
"additionalProperties": false
},
"group": {"$ref": "#/definitions/group"}
"group": {"$ref": "#/definitions/group"},
"login": {"$ref": "#/definitions/login"}
},
"additionalProperties": false
},
Expand All @@ -192,6 +193,11 @@
}
},
"additionalProperties": false
},
"login": {
"description": "Login to Azure",
"type": "object",
"additionalProperties": false
}
},
"type": "object",
Expand Down

0 comments on commit 95b58da

Please sign in to comment.