-
Notifications
You must be signed in to change notification settings - Fork 434
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1976 from leonweecs/leonweecs/vault-aws-auth
Add AWS auth method for Vault RA mode
- Loading branch information
Showing
5 changed files
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package aws | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/hashicorp/vault/api/auth/aws" | ||
) | ||
|
||
// AuthOptions defines the configuration options added using the | ||
// VaultOptions.AuthOptions field when AuthType is aws. | ||
// This maps directly to Vault's AWS Login options, | ||
// see: https://developer.hashicorp.com/vault/api-docs/auth/aws#login | ||
type AuthOptions struct { | ||
Role string `json:"role,omitempty"` | ||
Region string `json:"region,omitempty"` | ||
AwsAuthType string `json:"awsAuthType,omitempty"` | ||
|
||
// options specific to 'iam' auth type | ||
IamServerIDHeader string `json:"iamServerIdHeader"` | ||
|
||
// options specific to 'ec2' auth type | ||
SignatureType string `json:"signatureType,omitempty"` | ||
Nonce string `json:"nonce,omitempty"` | ||
} | ||
|
||
func NewAwsAuthMethod(mountPath string, options json.RawMessage) (*aws.AWSAuth, error) { | ||
var opts *AuthOptions | ||
|
||
err := json.Unmarshal(options, &opts) | ||
if err != nil { | ||
return nil, fmt.Errorf("error decoding AWS auth options: %w", err) | ||
} | ||
|
||
var awsAuth *aws.AWSAuth | ||
|
||
var loginOptions []aws.LoginOption | ||
if mountPath != "" { | ||
loginOptions = append(loginOptions, aws.WithMountPath(mountPath)) | ||
} | ||
if opts.Role != "" { | ||
loginOptions = append(loginOptions, aws.WithRole(opts.Role)) | ||
} | ||
if opts.Region != "" { | ||
loginOptions = append(loginOptions, aws.WithRegion(opts.Region)) | ||
} | ||
|
||
switch opts.AwsAuthType { | ||
case "iam": | ||
loginOptions = append(loginOptions, aws.WithIAMAuth()) | ||
|
||
if opts.IamServerIDHeader != "" { | ||
loginOptions = append(loginOptions, aws.WithIAMServerIDHeader(opts.IamServerIDHeader)) | ||
} | ||
case "ec2": | ||
loginOptions = append(loginOptions, aws.WithEC2Auth()) | ||
|
||
switch opts.SignatureType { | ||
case "pkcs7": | ||
loginOptions = append(loginOptions, aws.WithPKCS7Signature()) | ||
case "identity": | ||
loginOptions = append(loginOptions, aws.WithIdentitySignature()) | ||
case "rsa2048": | ||
loginOptions = append(loginOptions, aws.WithRSA2048Signature()) | ||
case "": | ||
// no-op | ||
default: | ||
return nil, fmt.Errorf("unknown SignatureType type %q; valid options are 'pkcs7', 'identity' and 'rsa2048'", opts.SignatureType) | ||
} | ||
|
||
if opts.Nonce != "" { | ||
loginOptions = append(loginOptions, aws.WithNonce(opts.Nonce)) | ||
} | ||
default: | ||
return nil, fmt.Errorf("unknown awsAuthType %q; valid options are 'iam' and 'ec2'", opts.AwsAuthType) | ||
} | ||
|
||
awsAuth, err = aws.NewAWSAuth(loginOptions...) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to initialize AWS auth method: %w", err) | ||
} | ||
|
||
return awsAuth, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package aws | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"net/url" | ||
"testing" | ||
|
||
vault "github.com/hashicorp/vault/api" | ||
) | ||
|
||
func testCAHelper(t *testing.T) (*url.URL, *vault.Client) { | ||
t.Helper() | ||
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
switch { | ||
case r.RequestURI == "/v1/auth/aws/login": | ||
w.WriteHeader(http.StatusOK) | ||
fmt.Fprintf(w, `{ | ||
"auth": { | ||
"client_token": "hvs.0000" | ||
} | ||
}`) | ||
case r.RequestURI == "/v1/auth/custom-aws/login": | ||
w.WriteHeader(http.StatusOK) | ||
fmt.Fprintf(w, `{ | ||
"auth": { | ||
"client_token": "hvs.9999" | ||
} | ||
}`) | ||
default: | ||
w.WriteHeader(http.StatusNotFound) | ||
fmt.Fprintf(w, `{"error":"not found"}`) | ||
} | ||
})) | ||
t.Cleanup(func() { | ||
srv.Close() | ||
}) | ||
u, err := url.Parse(srv.URL) | ||
if err != nil { | ||
srv.Close() | ||
t.Fatal(err) | ||
} | ||
|
||
config := vault.DefaultConfig() | ||
config.Address = srv.URL | ||
|
||
client, err := vault.NewClient(config) | ||
if err != nil { | ||
srv.Close() | ||
t.Fatal(err) | ||
} | ||
|
||
return u, client | ||
} | ||
|
||
func TestAws_LoginMountPaths(t *testing.T) { | ||
_, client := testCAHelper(t) | ||
|
||
// Dummy AWS credentials is needed for Vault client to sign the STS request | ||
t.Setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE") | ||
t.Setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY") | ||
|
||
tests := []struct { | ||
name string | ||
mountPath string | ||
token string | ||
}{ | ||
{ | ||
name: "ok default mount path", | ||
mountPath: "", | ||
token: "hvs.0000", | ||
}, | ||
{ | ||
name: "ok explicit mount path", | ||
mountPath: "aws", | ||
token: "hvs.0000", | ||
}, | ||
{ | ||
name: "ok custom mount path", | ||
mountPath: "custom-aws", | ||
token: "hvs.9999", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
method, err := NewAwsAuthMethod(tt.mountPath, json.RawMessage(`{"role":"test-role","awsAuthType":"iam"}`)) | ||
if err != nil { | ||
t.Errorf("NewAwsAuthMethod() error = %v", err) | ||
return | ||
} | ||
|
||
secret, err := client.Auth().Login(context.Background(), method) | ||
if err != nil { | ||
t.Errorf("Login() error = %v", err) | ||
return | ||
} | ||
|
||
token, _ := secret.TokenID() | ||
if token != tt.token { | ||
t.Errorf("Token error got %v, expected %v", token, tt.token) | ||
return | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestAws_NewAwsAuthMethod(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
mountPath string | ||
raw string | ||
wantErr bool | ||
}{ | ||
{ | ||
"ok iam", | ||
"", | ||
`{"role":"test-role","awsAuthType":"iam"}`, | ||
false, | ||
}, | ||
{ | ||
"ok iam with region", | ||
"", | ||
`{"role":"test-role","awsAuthType":"iam","region":"us-east-1"}`, | ||
false, | ||
}, | ||
{ | ||
"ok iam with header", | ||
"", | ||
`{"role":"test-role","awsAuthType":"iam","iamServerIdHeader":"vault.example.com"}`, | ||
false, | ||
}, | ||
{ | ||
"ok ec2", | ||
"", | ||
`{"role":"test-role","awsAuthType":"ec2"}`, | ||
false, | ||
}, | ||
{ | ||
"ok ec2 with nonce", | ||
"", | ||
`{"role":"test-role","awsAuthType":"ec2","nonce": "0000-0000-0000-0000"}`, | ||
false, | ||
}, | ||
{ | ||
"ok ec2 with signature type", | ||
"", | ||
`{"role":"test-role","awsAuthType":"ec2","signatureType":"rsa2048"}`, | ||
false, | ||
}, | ||
{ | ||
"fail mandatory role", | ||
"", | ||
`{}`, | ||
true, | ||
}, | ||
{ | ||
"fail mandatory auth type", | ||
"", | ||
`{"role":"test-role"}`, | ||
true, | ||
}, | ||
{ | ||
"fail invalid auth type", | ||
"", | ||
`{"role":"test-role","awsAuthType":"test"}`, | ||
true, | ||
}, | ||
{ | ||
"fail invalid ec2 signature type", | ||
"", | ||
`{"role":"test-role","awsAuthType":"test","signatureType":"test"}`, | ||
true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
_, err := NewAwsAuthMethod(tt.mountPath, json.RawMessage(tt.raw)) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Aws.NewAwsAuthMethod() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.