914 lines
25 KiB
Go
914 lines
25 KiB
Go
package aws_signing_helper
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math/big"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
|
)
|
|
|
|
const TestCredentialsFilePath = "/tmp/credentials"
|
|
|
|
func setup() error {
|
|
generateCredentialProcessDataScript := exec.Command("/bin/bash", "../generate-credential-process-data.sh")
|
|
_, err := generateCredentialProcessDataScript.Output()
|
|
return err
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
err := setup()
|
|
if err != nil {
|
|
log.Println(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
code := m.Run()
|
|
os.Exit(code)
|
|
}
|
|
|
|
// Simple struct to define fixtures
|
|
type CertData struct {
|
|
CertPath string
|
|
KeyType string
|
|
}
|
|
|
|
// Certificate fixtures should be generated by the script ./generate-certs.sh
|
|
// if they do not exist, or need to be updated.
|
|
func TestReadCertificateData(t *testing.T) {
|
|
fixtures := []CertData{
|
|
{"../tst/certs/ec-prime256v1-sha256-cert.pem", "EC"},
|
|
{"../tst/certs/rsa-2048-sha256-cert.pem", "RSA"},
|
|
}
|
|
for _, fixture := range fixtures {
|
|
certData, _, err := ReadCertificateData(fixture.CertPath)
|
|
|
|
if err != nil {
|
|
t.Log("Failed to read certificate data")
|
|
t.Fail()
|
|
}
|
|
|
|
if certData.KeyType != fixture.KeyType {
|
|
t.Logf("Wrong key type. Expected %s, got %s", fixture.KeyType, certData.KeyType)
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadInvalidCertificateData(t *testing.T) {
|
|
_, _, err := ReadCertificateData("../tst/certs/invalid-rsa-cert.pem")
|
|
if err == nil || !strings.Contains(err.Error(), "could not parse certificate") {
|
|
t.Log("Failed to throw a handled error")
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
func TestReadCertificateBundleData(t *testing.T) {
|
|
fixtures := []string{
|
|
"../tst/certs/cert-bundle.pem",
|
|
"../tst/certs/cert-bundle-with-comments.pem",
|
|
}
|
|
|
|
for _, fixture := range fixtures {
|
|
_, err := ReadCertificateBundleData(fixture)
|
|
if err != nil {
|
|
t.Log("Failed to read certificate bundle data")
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadPrivateKeyData(t *testing.T) {
|
|
fixtures := []string{
|
|
"../tst/certs/ec-prime256v1-key.pem",
|
|
"../tst/certs/ec-prime256v1-key-pkcs8.pem",
|
|
"../tst/certs/rsa-2048-key.pem",
|
|
"../tst/certs/rsa-2048-key-pkcs8.pem",
|
|
}
|
|
|
|
for _, fixture := range fixtures {
|
|
_, err := ReadPrivateKeyData(fixture)
|
|
|
|
if err != nil {
|
|
t.Log(fixture)
|
|
t.Log(err)
|
|
t.Log("Failed to read private key data")
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadInvalidPrivateKeyData(t *testing.T) {
|
|
_, err := ReadPrivateKeyData("../tst/certs/invalid-rsa-key.pem")
|
|
if err == nil || !strings.Contains(err.Error(), "unable to parse private key") {
|
|
t.Log("Failed to throw a handled error")
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
func TestBuildAuthorizationHeader(t *testing.T) {
|
|
testRequest, err := http.NewRequest("POST", "https://rolesanywhere.us-west-2.amazonaws.com", nil)
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Fail()
|
|
}
|
|
|
|
path := "../tst/certs/rsa-2048-sha256-cert.pem"
|
|
certificateList1, _ := ReadCertificateBundleData(path)
|
|
certificate1 := certificateList1[0]
|
|
pkPath := "../tst/certs/rsa-2048-key.pem"
|
|
|
|
awsRequest := request.Request{HTTPRequest: testRequest}
|
|
signer, signingAlgorithm, err := GetFileSystemSigner(pkPath, "", path, false)
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Fail()
|
|
}
|
|
certificate, err := signer.Certificate()
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Fail()
|
|
}
|
|
if !bytes.Equal(certificate.Raw, certificate1.Raw) {
|
|
t.Log("Certificate does not match signer certificate")
|
|
t.Fail()
|
|
}
|
|
certificateChain, err := signer.CertificateChain()
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Fail()
|
|
}
|
|
for i, cert := range certificateChain {
|
|
if !bytes.Equal(cert.Raw, certificateList1[i].Raw) {
|
|
t.Log("Certificate chain does not match signer certificate chain")
|
|
t.Fail()
|
|
}
|
|
}
|
|
requestSignFunction := CreateRequestSignFunction(signer, signingAlgorithm, certificate, certificateChain)
|
|
requestSignFunction(&awsRequest)
|
|
|
|
certificateList2, _ := ReadCertificateBundleData("../tst/certs/rsa-2048-2-sha256-cert.pem")
|
|
certificate2 := certificateList2[0]
|
|
os.Rename("../tst/certs/rsa-2048-sha256-cert.pem", "../tst/certs/rsa-2048-sha256-cert.pem.bak")
|
|
os.Rename("../tst/certs/rsa-2048-2-sha256-cert.pem", "../tst/certs/rsa-2048-sha256-cert.pem")
|
|
certificate, err = signer.Certificate()
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Fail()
|
|
}
|
|
if !bytes.Equal(certificate.Raw, certificate2.Raw) {
|
|
t.Log("Certificate does not match signer certificate after update")
|
|
t.Fail()
|
|
}
|
|
certificateChain, err = signer.CertificateChain()
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Fail()
|
|
}
|
|
for i, cert := range certificateChain {
|
|
if !bytes.Equal(cert.Raw, certificateList2[i].Raw) {
|
|
t.Log("Certificate chain does not match signer certificate chain after update")
|
|
t.Fail()
|
|
}
|
|
}
|
|
os.Rename("../tst/certs/rsa-2048-sha256-cert.pem", "../tst/certs/rsa-2048-2-sha256-cert.pem")
|
|
os.Rename("../tst/certs/rsa-2048-sha256-cert.pem.bak", "../tst/certs/rsa-2048-sha256-cert.pem")
|
|
requestSignFunction2 := CreateRequestSignFunction(signer, signingAlgorithm, certificate, certificateChain)
|
|
requestSignFunction2(&awsRequest)
|
|
}
|
|
|
|
// Verify that the provided payload was signed correctly with the provided options.
|
|
// This function is specifically used for unit testing.
|
|
func Verify(payload []byte, publicKey crypto.PublicKey, digest crypto.Hash, sig []byte) (bool, error) {
|
|
var hash []byte
|
|
switch digest {
|
|
case crypto.SHA256:
|
|
sum := sha256.Sum256(payload)
|
|
hash = sum[:]
|
|
case crypto.SHA384:
|
|
sum := sha512.Sum384(payload)
|
|
hash = sum[:]
|
|
case crypto.SHA512:
|
|
sum := sha512.Sum512(payload)
|
|
hash = sum[:]
|
|
default:
|
|
log.Fatal("unsupported digest")
|
|
return false, errors.New("unsupported digest")
|
|
}
|
|
|
|
{
|
|
publicKey, ok := publicKey.(*ecdsa.PublicKey)
|
|
if ok {
|
|
valid := ecdsa.VerifyASN1(publicKey, hash, sig)
|
|
return valid, nil
|
|
}
|
|
}
|
|
|
|
{
|
|
publicKey, ok := publicKey.(*rsa.PublicKey)
|
|
if ok {
|
|
err := rsa.VerifyPKCS1v15(publicKey, digest, hash, sig)
|
|
return err == nil, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func TestSign(t *testing.T) {
|
|
msg := "test message"
|
|
testTable := []CredentialsOpts{}
|
|
|
|
// TODO: Include tests for PKCS#12 containers, once fixtures are created
|
|
// with end-entity certificates.
|
|
ec_digests := []string{"sha1", "sha256", "sha384", "sha512"}
|
|
ec_curves := []string{"prime256v1", "secp384r1"}
|
|
|
|
for _, digest := range ec_digests {
|
|
for _, curve := range ec_curves {
|
|
cert := fmt.Sprintf("../tst/certs/ec-%s-%s-cert.pem",
|
|
curve, digest)
|
|
key := fmt.Sprintf("../tst/certs/ec-%s-key.pem", curve)
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert,
|
|
PrivateKeyId: key,
|
|
})
|
|
|
|
key = fmt.Sprintf("../tst/certs/ec-%s-key-pkcs8.pem", curve)
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert,
|
|
PrivateKeyId: key,
|
|
})
|
|
}
|
|
}
|
|
|
|
rsa_digests := []string{"md5", "sha1", "sha256", "sha384", "sha512"}
|
|
rsa_key_lengths := []string{"1024", "2048", "4096"}
|
|
|
|
for _, digest := range rsa_digests {
|
|
for _, keylen := range rsa_key_lengths {
|
|
cert := fmt.Sprintf("../tst/certs/rsa-%s-%s-cert.pem",
|
|
keylen, digest)
|
|
key := fmt.Sprintf("../tst/certs/rsa-%s-key.pem", keylen)
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert,
|
|
PrivateKeyId: key,
|
|
})
|
|
|
|
key = fmt.Sprintf("../tst/certs/rsa-%s-key-pkcs8.pem", keylen)
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert,
|
|
PrivateKeyId: key,
|
|
})
|
|
}
|
|
}
|
|
|
|
pkcs11_objects := []string{"rsa-2048", "ec-prime256v1"}
|
|
|
|
for _, object := range pkcs11_objects {
|
|
base_pkcs11_uri := "pkcs11:token=credential-helper-test?pin-value=1234"
|
|
basic_pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s?pin-value=1234", object)
|
|
always_auth_pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s-always-auth?pin-value=1234", object)
|
|
cert_file := fmt.Sprintf("../tst/certs/%s-sha256-cert.pem", object)
|
|
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: basic_pkcs11_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
PrivateKeyId: basic_pkcs11_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: basic_pkcs11_uri,
|
|
PrivateKeyId: basic_pkcs11_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert_file,
|
|
PrivateKeyId: basic_pkcs11_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: basic_pkcs11_uri,
|
|
PrivateKeyId: always_auth_pkcs11_uri,
|
|
ReusePin: true,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert_file,
|
|
PrivateKeyId: always_auth_pkcs11_uri,
|
|
ReusePin: true,
|
|
})
|
|
// Note that for the below test case, there are two matching keys.
|
|
// Both keys will validate with the certificate, and one will be chosen
|
|
// (it doesn't matter which, since both are the exact same key - it's
|
|
// just that one has the CKA_ALWAYS_AUTHENTICATE attribute set).
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert_file,
|
|
PrivateKeyId: base_pkcs11_uri,
|
|
ReusePin: true,
|
|
})
|
|
}
|
|
|
|
digestList := []crypto.Hash{crypto.SHA256, crypto.SHA384, crypto.SHA512}
|
|
|
|
for _, credOpts := range testTable {
|
|
signer, _, err := GetSigner(&credOpts)
|
|
if err != nil {
|
|
var logMsg string
|
|
if credOpts.CertificateId != "" || credOpts.PrivateKeyId != "" {
|
|
logMsg = fmt.Sprintf("Failed to get signer for '%s'/'%s'",
|
|
credOpts.CertificateId, credOpts.PrivateKeyId)
|
|
} else {
|
|
logMsg = fmt.Sprintf("Failed to get signer for '%s'",
|
|
credOpts.CertIdentifier.Subject)
|
|
}
|
|
t.Log(logMsg)
|
|
t.Fail()
|
|
return
|
|
}
|
|
|
|
pubKey := signer.Public()
|
|
if credOpts.CertificateId != "" && pubKey == nil {
|
|
t.Log(fmt.Sprintf("Signer didn't provide public key for '%s'/'%s'",
|
|
credOpts.CertificateId, credOpts.PrivateKeyId))
|
|
t.Fail()
|
|
return
|
|
}
|
|
|
|
for _, digest := range digestList {
|
|
signatureBytes, err := signer.Sign(rand.Reader, []byte(msg), digest)
|
|
// Try signing again to make sure that there aren't any issues
|
|
// with reopening sessions. Also, in some test cases, signing again
|
|
// makes sure that the context-specific PIN was saved.
|
|
signer.Sign(rand.Reader, []byte(msg), digest)
|
|
if err != nil {
|
|
t.Log("Failed to sign the input message")
|
|
t.Fail()
|
|
return
|
|
}
|
|
_, err = signer.Sign(rand.Reader, []byte(msg), digest)
|
|
if err != nil {
|
|
t.Log("Failed second signature on the input message")
|
|
t.Fail()
|
|
return
|
|
}
|
|
|
|
if pubKey != nil {
|
|
valid, _ := Verify([]byte(msg), pubKey, digest, signatureBytes)
|
|
if !valid {
|
|
t.Log(fmt.Sprintf("Failed to verify the signature for '%s'/'%s'",
|
|
credOpts.CertificateId, credOpts.PrivateKeyId))
|
|
t.Fail()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
signer.Close()
|
|
}
|
|
}
|
|
|
|
func TestCredentialProcess(t *testing.T) {
|
|
testTable := []struct {
|
|
name string
|
|
server *httptest.Server
|
|
}{
|
|
{
|
|
name: "create-session-server-response",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
},
|
|
}
|
|
for _, tc := range testTable {
|
|
credentialsOpts := CredentialsOpts{
|
|
PrivateKeyId: "../credential-process-data/client-key.pem",
|
|
CertificateId: "../credential-process-data/client-cert.pem",
|
|
RoleArn: "arn:aws:iam::000000000000:role/ExampleS3WriteRole",
|
|
ProfileArnStr: "arn:aws:rolesanywhere:us-east-1:000000000000:profile/41cl0bae-6783-40d4-ab20-65dc5d922e45",
|
|
TrustAnchorArnStr: "arn:aws:rolesanywhere:us-east-1:000000000000:trust-anchor/41cl0bae-6783-40d4-ab20-65dc5d922e45",
|
|
Endpoint: tc.server.URL,
|
|
SessionDuration: 900,
|
|
}
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
defer tc.server.Close()
|
|
signer, signatureAlgorithm, err := GetSigner(&credentialsOpts)
|
|
if err != nil {
|
|
t.Log("Failed to get signer")
|
|
t.Fail()
|
|
return
|
|
}
|
|
resp, err := GenerateCredentials(&credentialsOpts, signer, signatureAlgorithm)
|
|
|
|
if err != nil {
|
|
t.Log(err)
|
|
t.Log("Unable to call credential-process")
|
|
t.Fail()
|
|
}
|
|
|
|
if resp.AccessKeyId != "accessKeyId" {
|
|
t.Log("Incorrect access key id")
|
|
t.Fail()
|
|
}
|
|
if resp.SecretAccessKey != "secretAccessKey" {
|
|
t.Log("Incorrect secret access key")
|
|
t.Fail()
|
|
}
|
|
if resp.SessionToken != "sessionToken" {
|
|
t.Log("Incorrect session token")
|
|
t.Fail()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCertStoreSignerCreationFails(t *testing.T) {
|
|
testTable := []CredentialsOpts{}
|
|
|
|
randomLargeSerial := new(big.Int)
|
|
randomLargeSerial.SetString("123456719012345678901234567890", 10)
|
|
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertIdentifier: CertIdentifier{
|
|
Subject: "invalid-subject",
|
|
},
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertIdentifier: CertIdentifier{
|
|
Issuer: "invalid-issuer",
|
|
},
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertIdentifier: CertIdentifier{
|
|
SerialNumber: randomLargeSerial,
|
|
},
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertIdentifier: CertIdentifier{
|
|
Subject: "CN=roles-anywhere-rsa-2048-sha25",
|
|
SerialNumber: randomLargeSerial,
|
|
},
|
|
})
|
|
|
|
for _, credOpts := range testTable {
|
|
_, _, err := GetSigner(&credOpts)
|
|
if err == nil {
|
|
t.Log("Expected failure when creating certificate store signer, but received none")
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSignerCreationFails(t *testing.T) {
|
|
var cert string
|
|
testTable := []CredentialsOpts{}
|
|
|
|
ec_digests := []string{"sha1", "sha256", "sha384", "sha512"}
|
|
ec_curves := []string{"prime256v1", "secp384r1"}
|
|
|
|
for _, digest := range ec_digests {
|
|
for _, curve := range ec_curves {
|
|
cert = fmt.Sprintf("../tst/certs/ec-%s-%s.p12",
|
|
curve, digest)
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert,
|
|
})
|
|
}
|
|
}
|
|
|
|
rsa_digests := []string{"md5", "sha1", "sha256", "sha384", "sha512"}
|
|
rsa_key_lengths := []string{"1024", "2048", "4096"}
|
|
|
|
for _, digest := range rsa_digests {
|
|
for _, keylen := range rsa_key_lengths {
|
|
cert = fmt.Sprintf("../tst/certs/rsa-%s-%s.p12",
|
|
keylen, digest)
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: cert,
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, credOpts := range testTable {
|
|
_, _, err := GetSigner(&credOpts)
|
|
// We expect a failure since the certificates in these .p12 files are
|
|
// self-signed. When creating a signer, we expect there to be an
|
|
// end-entity certificate within the container.
|
|
if err == nil {
|
|
t.Log("Expected failure when creating PKCS#12 signer, but received none")
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPKCS11SignerCreationFails(t *testing.T) {
|
|
testTable := []CredentialsOpts{}
|
|
|
|
template_uri := "pkcs11:token=credential-helper-test;object=%s?pin-value=1234"
|
|
rsa_generic_uri := fmt.Sprintf(template_uri, "rsa-2048")
|
|
ec_generic_uri := fmt.Sprintf(template_uri, "ec-prime256v1")
|
|
always_auth_rsa_uri := fmt.Sprintf(template_uri, "rsa-2048-always-auth")
|
|
always_auth_ec_uri := fmt.Sprintf(template_uri, "ec-prime256v1-always-auth")
|
|
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: rsa_generic_uri,
|
|
PrivateKeyId: ec_generic_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: ec_generic_uri,
|
|
PrivateKeyId: rsa_generic_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem",
|
|
PrivateKeyId: rsa_generic_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem",
|
|
PrivateKeyId: ec_generic_uri,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: rsa_generic_uri,
|
|
PrivateKeyId: always_auth_ec_uri,
|
|
ReusePin: true,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: ec_generic_uri,
|
|
PrivateKeyId: always_auth_rsa_uri,
|
|
ReusePin: true,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem",
|
|
PrivateKeyId: always_auth_rsa_uri,
|
|
ReusePin: true,
|
|
})
|
|
testTable = append(testTable, CredentialsOpts{
|
|
CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem",
|
|
PrivateKeyId: always_auth_ec_uri,
|
|
ReusePin: true,
|
|
})
|
|
|
|
for _, credOpts := range testTable {
|
|
_, _, err := GetSigner(&credOpts)
|
|
if err == nil {
|
|
t.Log("Expected failure when creating PKCS#11 signer, but received none")
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
testTable := []struct {
|
|
name string
|
|
server *httptest.Server
|
|
inputFileContents string
|
|
profile string
|
|
expectedFileContents string
|
|
}{
|
|
{
|
|
name: "test-space-separated-keys",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
inputFileContents: `test
|
|
test
|
|
test
|
|
[test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
profile: "test profile",
|
|
expectedFileContents: `test
|
|
test
|
|
test
|
|
[test profile]
|
|
aws_access_key_id = accessKeyId
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
},
|
|
{
|
|
name: "test-profile-with-other-keys",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
inputFileContents: `test
|
|
test
|
|
test
|
|
[test profile]
|
|
aws_access_key_id = test
|
|
test_key = test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
profile: "test profile",
|
|
expectedFileContents: `test
|
|
test
|
|
test
|
|
[test profile]
|
|
aws_access_key_id = accessKeyId
|
|
test_key = test
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
},
|
|
{
|
|
name: "test-commented-profile",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
inputFileContents: `test
|
|
test
|
|
test
|
|
# [test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
profile: "test profile",
|
|
expectedFileContents: `test
|
|
test
|
|
test
|
|
# [test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_secret_access_key = test
|
|
[test profile]
|
|
aws_access_key_id = accessKeyId
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken
|
|
`,
|
|
},
|
|
{
|
|
name: "test-profile-does-not-exist",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
inputFileContents: `test
|
|
test
|
|
test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
profile: "test profile",
|
|
expectedFileContents: `test
|
|
test
|
|
test
|
|
[test]
|
|
aws_secret_access_key = test
|
|
[test profile]
|
|
aws_access_key_id = accessKeyId
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken
|
|
`,
|
|
},
|
|
{
|
|
name: "test-first-word-in-profile-matches",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
inputFileContents: `test
|
|
test
|
|
test
|
|
[test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
profile: "test",
|
|
expectedFileContents: `test
|
|
test
|
|
test
|
|
[test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_access_key_id = accessKeyId
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken`,
|
|
},
|
|
{
|
|
name: "test-multiple-profiles-with-same-name",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
inputFileContents: `test
|
|
test
|
|
test
|
|
[test]
|
|
test_key = test
|
|
[test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
profile: "test",
|
|
expectedFileContents: `test
|
|
test
|
|
test
|
|
[test]
|
|
test_key = test
|
|
aws_access_key_id = accessKeyId
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken
|
|
[test profile]
|
|
aws_access_key_id = test
|
|
[test]
|
|
aws_secret_access_key = test`,
|
|
},
|
|
}
|
|
for _, tc := range testTable {
|
|
credentialsOpts := CredentialsOpts{
|
|
PrivateKeyId: "../credential-process-data/client-key.pem",
|
|
CertificateId: "../credential-process-data/client-cert.pem",
|
|
RoleArn: "arn:aws:iam::000000000000:role/ExampleS3WriteRole",
|
|
ProfileArnStr: "arn:aws:rolesanywhere:us-east-1:000000000000:profile/41cl0bae-6783-40d4-ab20-65dc5d922e45",
|
|
TrustAnchorArnStr: "arn:aws:rolesanywhere:us-east-1:000000000000:trust-anchor/41cl0bae-6783-40d4-ab20-65dc5d922e45",
|
|
Endpoint: tc.server.URL,
|
|
SessionDuration: 900,
|
|
}
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
SetupTests()
|
|
defer tc.server.Close()
|
|
os.Setenv(AwsSharedCredentialsFileEnvVarName, TestCredentialsFilePath)
|
|
_, err := GetCredentialsFileContents() // first create the credentials file with the appropriate permissions
|
|
if err != nil {
|
|
t.Log("unable to create credentials file for testing")
|
|
t.Fail()
|
|
}
|
|
writeOnlyCredentialsFile, err := GetWriteOnlyCredentialsFile() // then obtain a handle to the credentials file to perform write operations
|
|
if err != nil {
|
|
t.Log("unable to write to credentials file for testing")
|
|
t.Fail()
|
|
}
|
|
defer writeOnlyCredentialsFile.Close()
|
|
writeOnlyCredentialsFile.WriteString(tc.inputFileContents)
|
|
|
|
Update(credentialsOpts, tc.profile, true)
|
|
|
|
fileByteContents, _ := ioutil.ReadFile(TestCredentialsFilePath)
|
|
fileStringContents := trimLastChar(string(fileByteContents))
|
|
if fileStringContents != tc.expectedFileContents {
|
|
t.Log("unexpected file contents")
|
|
t.Fail()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateFilePermissions(t *testing.T) {
|
|
testTable := []struct {
|
|
name string
|
|
server *httptest.Server
|
|
profile string
|
|
expectedFileContents string
|
|
}{
|
|
{
|
|
name: "test-space-separated-keys",
|
|
server: GetMockedCreateSessionResponseServer(),
|
|
profile: "test profile",
|
|
expectedFileContents: `[test profile]
|
|
aws_access_key_id = accessKeyId
|
|
aws_secret_access_key = secretAccessKey
|
|
aws_session_token = sessionToken
|
|
`,
|
|
},
|
|
}
|
|
for _, tc := range testTable {
|
|
credentialsOpts := CredentialsOpts{
|
|
PrivateKeyId: "../credential-process-data/client-key.pem",
|
|
CertificateId: "../credential-process-data/client-cert.pem",
|
|
RoleArn: "arn:aws:iam::000000000000:role/ExampleS3WriteRole",
|
|
ProfileArnStr: "arn:aws:rolesanywhere:us-east-1:000000000000:profile/41cl0bae-6783-40d4-ab20-65dc5d922e45",
|
|
TrustAnchorArnStr: "arn:aws:rolesanywhere:us-east-1:000000000000:trust-anchor/41cl0bae-6783-40d4-ab20-65dc5d922e45",
|
|
Endpoint: tc.server.URL,
|
|
SessionDuration: 900,
|
|
}
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
SetupTests()
|
|
defer tc.server.Close()
|
|
os.Setenv(AwsSharedCredentialsFileEnvVarName, TestCredentialsFilePath)
|
|
|
|
Update(credentialsOpts, tc.profile, true)
|
|
|
|
fileByteContents, _ := ioutil.ReadFile(TestCredentialsFilePath)
|
|
fileStringContents := trimLastChar(string(fileByteContents))
|
|
if fileStringContents != tc.expectedFileContents {
|
|
t.Log("unexpected file contents")
|
|
t.Fail()
|
|
}
|
|
|
|
info, _ := os.Stat(TestCredentialsFilePath)
|
|
mode := info.Mode()
|
|
if mode != ((1 << 8) | (1 << 7)) {
|
|
t.Log("unexpected file mode")
|
|
t.Fail()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateLongToken(t *testing.T) {
|
|
_, err := GenerateToken(150)
|
|
if err == nil {
|
|
t.Log("token generation should've failed since token size is too large")
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
func TestGenerateToken(t *testing.T) {
|
|
token1, err := GenerateToken(100)
|
|
if err != nil {
|
|
t.Log("unexpected failure in generating token")
|
|
t.Fail()
|
|
}
|
|
|
|
token2, err := GenerateToken(100)
|
|
if err != nil {
|
|
t.Log("unexpected failure in generating token")
|
|
t.Fail()
|
|
}
|
|
|
|
if token1 == token2 {
|
|
t.Log("expected two randomly generated tokens to be different")
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
func TestStoreValidToken(t *testing.T) {
|
|
token, err := GenerateToken(100)
|
|
if err != nil {
|
|
t.Log("unexpected failure in generating token")
|
|
t.Fail()
|
|
}
|
|
|
|
err = InsertToken(token, time.Now().Add(time.Second*time.Duration(100)))
|
|
if err != nil {
|
|
t.Log("unexpected failure when inserting token")
|
|
t.Fail()
|
|
}
|
|
|
|
httpRequest, err := http.NewRequest("GET", "http://127.0.0.1", nil)
|
|
if err != nil {
|
|
t.Log("unable to create test http request")
|
|
t.Fail()
|
|
}
|
|
httpRequest.Header.Add(EC2_METADATA_TOKEN_HEADER, token)
|
|
|
|
err = CheckValidToken(nil, httpRequest)
|
|
if err != nil {
|
|
t.Log("expected previously inserted token to be valid")
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
func Test(t *testing.T) {
|
|
httpRequest, err := http.NewRequest("GET", "http://127.0.0.1", nil)
|
|
if err != nil {
|
|
t.Log("unable to create test http request")
|
|
t.Fail()
|
|
}
|
|
httpRequest.Header.Add("test-header", "test-header-value")
|
|
|
|
headerNames := [4]string{"Test-Header", "test-header", "TEST-HEADER", "tEST-hEadeR"}
|
|
for _, header := range headerNames {
|
|
testHeaderValue := httpRequest.Header.Get(header)
|
|
if testHeaderValue != "test-header-value" {
|
|
t.Log("header name canonicalization not working as expected")
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func SetupTests() {
|
|
os.Remove(TestCredentialsFilePath)
|
|
}
|
|
|
|
func trimLastChar(s string) string {
|
|
r, size := utf8.DecodeLastRuneInString(s)
|
|
if r == utf8.RuneError && (size == 0 || size == 1) {
|
|
size = 0
|
|
}
|
|
return s[:len(s)-size]
|
|
}
|
|
|
|
func GetMockedCreateSessionResponseServer() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{
|
|
"credentialSet":[
|
|
{
|
|
"assumedRoleUser": {
|
|
"arn": "arn:aws:sts::000000000000:assumed-role/ExampleS3WriteRole",
|
|
"assumedRoleId": "assumedRoleId"
|
|
},
|
|
"credentials":{
|
|
"accessKeyId": "accessKeyId",
|
|
"expiration": "2022-07-27T04:36:55Z",
|
|
"secretAccessKey": "secretAccessKey",
|
|
"sessionToken": "sessionToken"
|
|
},
|
|
"packedPolicySize": 10,
|
|
"roleArn": "arn:aws:iam::000000000000:role/ExampleS3WriteRole",
|
|
"sourceIdentity": "sourceIdentity"
|
|
}
|
|
],
|
|
"subjectArn": "arn:aws:rolesanywhere:us-east-1:000000000000:subject/41cl0bae-6783-40d4-ab20-65dc5d922e45"
|
|
}`))
|
|
}))
|
|
}
|