Documentation
¶
Overview ¶
Package awsdag signs in to AWS IAM Identity Center from a machine with no browser.
The sign-in the AWS CLI performs opens a browser and waits for a redirect back to localhost, neither of which exists on the far side of an ssh session. The OAuth 2.0 device authorization grant (RFC 8628) was designed for exactly that situation: this machine prints a URL and a code, a browser somewhere else approves them, and this machine polls until it has a token.
Nothing here needs credentials to start. The three ssooidc operations the grant uses are anonymous, so the only things that must be supplied are the Identity Center start URL and the region it lives in. Everything else -- which accounts the user can reach, which permission sets each one offers -- is discovered from the token afterwards.
Index ¶
- Constants
- Variables
- func Choose[T any](in io.Reader, out io.Writer, prompt string, items []T, label func(T) string) (T, error)
- func Notify(w io.Writer, auth *Authorization) error
- type Account
- type Authorization
- type Credentials
- type Format
- type OIDCAPI
- type Options
- type Profile
- type SSOAPI
- type Session
- func (s *Session) Accounts(ctx context.Context) ([]Account, error)
- func (s *Session) ChooseAccount(ctx context.Context, in io.Reader, out io.Writer, known string) (string, error)
- func (s *Session) ChooseRole(ctx context.Context, in io.Reader, out io.Writer, accountID, known string) (string, error)
- func (s *Session) Credentials(ctx context.Context, accountID, role string) (*Credentials, error)
- func (s *Session) Roles(ctx context.Context, accountID string) ([]string, error)
Constants ¶
const ClientName = "awsdag"
ClientName is the name this client registers under. It appears on the approval page in the browser, so it is what someone sees when deciding whether the thing asking for access is the thing they just ran.
Variables ¶
var ErrNoChoices = errors.New("nothing to choose from")
ErrNoChoices is returned when there is nothing to choose from, which for an account or a permission set means nothing has been assigned.
Formats are the accepted values of Format, in the order they should be offered.
Functions ¶
func Choose ¶
func Choose[T any](in io.Reader, out io.Writer, prompt string, items []T, label func(T) string) (T, error)
Choose asks which of items to use, and returns it.
A single item is taken without asking: there is no choice to make, and a prompt with one answer is just something else to press return on. Anything unreadable is asked again rather than treated as an answer, because the question is which set of credentials to mint and guessing is not an option.
func Notify ¶
func Notify(w io.Writer, auth *Authorization) error
Notify writes the verification URI and user code where someone will see them.
The default destination is standard error rather than standard output, because the credentials go to standard output and `eval $(awsdag ...)` would otherwise swallow the very instructions the caller has to act on.
Types ¶
type Authorization ¶
type Authorization struct {
// VerificationURI is the page to open; VerificationURIComplete is the
// same page with the code already filled in.
VerificationURI string
VerificationURIComplete string
// UserCode is what the page asks for, if it was opened without the code.
UserCode string
// ExpiresAt is when the code stops being accepted.
ExpiresAt time.Time
}
Authorization is what someone has to approve in a browser.
type Credentials ¶
type Credentials struct {
AccessKeyID string
SecretAccessKey string
SessionToken string
Expiration time.Time
}
Credentials are the short-term STS credentials issued for one permission set in one account.
Their lifetime is the session duration of the permission set -- an hour by default, twelve at most -- and has nothing to do with how long the session that produced them lasts.
type OIDCAPI ¶
type OIDCAPI interface {
RegisterClient(context.Context, *ssooidc.RegisterClientInput, ...func(*ssooidc.Options)) (*ssooidc.RegisterClientOutput, error)
StartDeviceAuthorization(context.Context, *ssooidc.StartDeviceAuthorizationInput, ...func(*ssooidc.Options)) (*ssooidc.StartDeviceAuthorizationOutput, error)
CreateToken(context.Context, *ssooidc.CreateTokenInput, ...func(*ssooidc.Options)) (*ssooidc.CreateTokenOutput, error)
}
OIDCAPI is the part of the ssooidc client the device authorization grant uses. All three operations resolve to anonymous auth, so nothing has to be signed and no credentials have to exist yet -- which is the whole reason this flow works on a machine that has none.
type Options ¶
type Options struct {
// StartURL is the AWS access portal URL of the Identity Center instance.
StartURL string
// Region is where that instance lives. It is not the region the caller
// intends to work in, and the two are often different.
Region string
// Notify presents the verification URI and user code to whoever is going
// to approve them. Nil prints to standard error, which keeps the prompt
// clear of the credentials on standard output.
Notify func(*Authorization) error
// OIDC and SSO stand in for the service. Nil builds real clients for
// Region.
OIDC OIDCAPI
SSO SSOAPI
// Clock and Sleep exist so the polling loop can be tested without
// waiting. Nil uses the real ones.
Clock func() time.Time
Sleep func(time.Duration)
}
Options are the two facts that cannot be discovered, plus the seams tests reach through.
type Profile ¶
Profile is what a profile in ~/.aws/config has to say about signing in.
The four fields are the four things a run needs, and the file is already laid out along the same seam: an [sso-session] holds the two that authentication needs, and the profile that names it holds the two that GetRoleCredentials needs. Either half may be missing, and a profile with no account or permission set is still worth reading for the other two.
func LoadProfile ¶
func LoadProfile(ctx context.Context, name string, optFns ...func(*config.LoadSharedConfigOptions)) (*Profile, error)
LoadProfile reads a profile out of the shared config.
Only the file is read. Credentials are not resolved and nothing is called, which matters because the sign-in this supplies the settings for is the one that has not happened yet.
type SSOAPI ¶
type SSOAPI interface {
ListAccounts(context.Context, *sso.ListAccountsInput, ...func(*sso.Options)) (*sso.ListAccountsOutput, error)
ListAccountRoles(context.Context, *sso.ListAccountRolesInput, ...func(*sso.Options)) (*sso.ListAccountRolesOutput, error)
GetRoleCredentials(context.Context, *sso.GetRoleCredentialsInput, ...func(*sso.Options)) (*sso.GetRoleCredentialsOutput, error)
}
SSOAPI is the part of the sso client that the access token unlocks. The token carries the caller's identity in a header, so these are anonymous too.
type Session ¶
type Session struct {
AccessToken string
ExpiresAt time.Time
// contains filtered or unexported fields
}
Session is an approved sign-in: an access token for the Identity Center instance as a whole, not for any one account or permission set. Every account and role assigned to the user can be reached through it until it expires, which makes it worth rather more than the credentials it hands out.
func Auth ¶
Auth runs the device authorization grant to completion and returns the session it produced. It blocks while someone approves the code in a browser, and gives up when the code expires or ctx is cancelled.
func (*Session) Accounts ¶
Accounts lists the accounts the session can reach.
This is the same list the AWS access portal shows, and it is bounded by the assignments in Identity Center: an account nobody gave the user is not in it, and asking for credentials there would fail anyway.
func (*Session) ChooseAccount ¶
func (s *Session) ChooseAccount(ctx context.Context, in io.Reader, out io.Writer, known string) (string, error)
ChooseAccount settles which account to use.
A known account is taken as it is. Listing to check that it is assigned would put a call in front of the one that matters and only move the same failure earlier, since GetRoleCredentials refuses an account nobody gave the user anyway.
func (*Session) ChooseRole ¶
func (s *Session) ChooseRole(ctx context.Context, in io.Reader, out io.Writer, accountID, known string) (string, error)
ChooseRole settles which permission set to use in an account.
func (*Session) Credentials ¶
Credentials exchanges the session for credentials in one account.
A session can do this as many times as it likes, for any account and permission set assigned to the user, without anyone returning to a browser.