Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/awstesting: Update SDK custom certificate testing utils #1626

Merged
merged 3 commits into from Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codegen.yml
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
go-version: [1.17]
go-version: [1.18]
env:
JAVA_TOOL_OPTIONS: "-Xmx2g"
steps:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
go-version: [1.17, 1.16, 1.15]
go-version: [1.18, 1.17, 1.16, 1.15]
steps:
- uses: actions/checkout@v2

Expand All @@ -38,7 +38,7 @@ jobs:
strategy:
matrix:
os: [windows-latest]
go-version: [1.17, 1.16, 1.15]
go-version: [1.18, 1.17, 1.16, 1.15]
env:
EACHMODULE_SKIP: "internal\\repotools\\changes"
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [1.17.x]
go-version: [1.18.x]
os: [ubuntu-latest] # other options: macos-latest, windows-latest
steps:
- uses: actions/checkout@v2
Expand Down
291 changes: 291 additions & 0 deletions internal/awstesting/certificate_utils.go
@@ -0,0 +1,291 @@
package awstesting

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/http"
"net/http/httptest"
"os"
"strings"
"time"
)

var (
// TLSBundleCA is the CA PEM
TLSBundleCA []byte

// TLSBundleCert is the Server PEM
TLSBundleCert []byte

// TLSBundleKey is the Server private key PEM
TLSBundleKey []byte

// ClientTLSCert is the Client PEM
ClientTLSCert []byte

// ClientTLSKey is the Client private key PEM
ClientTLSKey []byte
)

func init() {
caPEM, _, caCert, caPrivKey, err := generateRootCA()
if err != nil {
panic("failed to generate testing root CA, " + err.Error())
}
TLSBundleCA = caPEM

serverCertPEM, serverCertPrivKeyPEM, err := generateLocalCert(caCert, caPrivKey)
if err != nil {
panic("failed to generate testing server cert, " + err.Error())
}
TLSBundleCert = serverCertPEM
TLSBundleKey = serverCertPrivKeyPEM

clientCertPEM, clientCertPrivKeyPEM, err := generateLocalCert(caCert, caPrivKey)
if err != nil {
panic("failed to generate testing client cert, " + err.Error())
}
ClientTLSCert = clientCertPEM
ClientTLSKey = clientCertPrivKeyPEM
}

func generateRootCA() (
caPEM, caPrivKeyPEM []byte, caCert *x509.Certificate, caPrivKey *rsa.PrivateKey, err error,
) {
caCert = &x509.Certificate{
SerialNumber: big.NewInt(42),
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"AWS SDK for Go Test Certificate"},
CommonName: "Test Root CA",
},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
IsCA: true,
}

// Create CA private and public key
caPrivKey, err = rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed generate CA RSA key, %w", err)
}

// Create CA certificate
caBytes, err := x509.CreateCertificate(rand.Reader, caCert, caCert, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed generate CA certificate, %w", err)
}

// PEM encode CA certificate and private key
var caPEMBuf bytes.Buffer
pem.Encode(&caPEMBuf, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})

var caPrivKeyPEMBuf bytes.Buffer
pem.Encode(&caPrivKeyPEMBuf, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
})

return caPEMBuf.Bytes(), caPrivKeyPEMBuf.Bytes(), caCert, caPrivKey, nil
}

func generateLocalCert(parentCert *x509.Certificate, parentPrivKey *rsa.PrivateKey) (
certPEM, certPrivKeyPEM []byte, err error,
) {
cert := &x509.Certificate{
SerialNumber: big.NewInt(42),
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"AWS SDK for Go Test Certificate"},
CommonName: "Test Root CA",
},
IPAddresses: []net.IP{
net.IPv4(127, 0, 0, 1),
net.IPv6loopback,
},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().AddDate(1, 0, 0),
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature,
}

// Create server private and public key
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate server RSA private key, %w", err)
}

// Create server certificate
certBytes, err := x509.CreateCertificate(rand.Reader, cert, parentCert, &certPrivKey.PublicKey, parentPrivKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate server certificate, %w", err)
}

// PEM encode certificate and private key
var certPEMBuf bytes.Buffer
pem.Encode(&certPEMBuf, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})

var certPrivKeyPEMBuf bytes.Buffer
pem.Encode(&certPrivKeyPEMBuf, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
})

return certPEMBuf.Bytes(), certPrivKeyPEMBuf.Bytes(), nil
}

// NewTLSClientCertServer creates a new HTTP test server initialize to require
// HTTP clients authenticate with TLS client certificates.
func NewTLSClientCertServer(handler http.Handler) (*httptest.Server, error) {
server := httptest.NewUnstartedServer(handler)

if server.TLS == nil {
server.TLS = &tls.Config{}
}
server.TLS.ClientAuth = tls.RequireAndVerifyClientCert

if server.TLS.ClientCAs == nil {
server.TLS.ClientCAs = x509.NewCertPool()
}
certPem := append(ClientTLSCert, ClientTLSKey...)
if ok := server.TLS.ClientCAs.AppendCertsFromPEM(certPem); !ok {
return nil, fmt.Errorf("failed to append client certs")
}

return server, nil
}

// CreateClientTLSCertFiles returns a set of temporary files for the client
// certificate and key files.
func CreateClientTLSCertFiles() (cert, key string, err error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these actually need to be written out to disk?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the v1 SDK yes since it supports the AWS_SDK_GO_CLIENT_TLS_KEY environment variables. Though it doesn't look like the v2 SDK has this behavior for some reason. Will have to investigate why that is missing.

cert, err = createTmpFile(ClientTLSCert)
if err != nil {
return "", "", err
}

key, err = createTmpFile(ClientTLSKey)
if err != nil {
return "", "", err
}

return cert, key, nil
}

func availableLocalAddr(ip string) (v string, err error) {
l, err := net.Listen("tcp", ip+":0")
if err != nil {
return "", err
}
defer func() {
closeErr := l.Close()
if err == nil {
err = closeErr
} else if closeErr != nil {
err = fmt.Errorf("ip listener close error: %v, original error: %w", closeErr, err)
}
}()

return l.Addr().String(), nil
}

// CreateTLSServer will create the TLS server on an open port using the
// certificate and key. The address will be returned that the server is running on.
func CreateTLSServer(cert, key string, mux *http.ServeMux) (string, error) {
addr, err := availableLocalAddr("127.0.0.1")
if err != nil {
return "", err
}

if mux == nil {
mux = http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
}

go func() {
if err := http.ListenAndServeTLS(addr, cert, key, mux); err != nil {
panic(err)
}
}()

for i := 0; i < 60; i++ {
if _, err := http.Get("https://" + addr); err != nil && !strings.Contains(err.Error(), "connection refused") {
break
}

time.Sleep(1 * time.Second)
}

return "https://" + addr, nil
}

// CreateTLSBundleFiles returns the temporary filenames for the certificate
// key, and CA PEM content. These files should be deleted when no longer
// needed. CleanupTLSBundleFiles can be used for this cleanup.
func CreateTLSBundleFiles() (cert, key, ca string, err error) {
cert, err = createTmpFile(TLSBundleCert)
if err != nil {
return "", "", "", err
}

key, err = createTmpFile(TLSBundleKey)
if err != nil {
return "", "", "", err
}

ca, err = createTmpFile(TLSBundleCA)
if err != nil {
return "", "", "", err
}

return cert, key, ca, nil
}

// CleanupTLSBundleFiles takes variadic list of files to be deleted.
func CleanupTLSBundleFiles(files ...string) error {
for _, file := range files {
if err := os.Remove(file); err != nil {
return err
}
}

return nil
}

func createTmpFile(b []byte) (string, error) {
bundleFile, err := ioutil.TempFile(os.TempDir(), "aws-sdk-go-session-test")
if err != nil {
return "", err
}

_, err = bundleFile.Write(b)
if err != nil {
return "", err
}

defer bundleFile.Close()
return bundleFile.Name(), nil
}