Jeremy Stott 6b8e6fc2c2 Initial commit of sshrimp.
* sshrimp-agent and sshrimp-ca building and deploying.
* mage build system working.
* successful deploy and SSH to host.
* need to tidy up and add tests.
2020-02-18 23:45:55 +13:00

159 lines
4.5 KiB
Go

package main
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"math"
"math/big"
"regexp"
"time"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-lambda-go/lambdacontext"
"github.com/stoggi/sshrimp/internal/config"
"github.com/stoggi/sshrimp/internal/identity"
"github.com/stoggi/sshrimp/internal/signer"
"golang.org/x/crypto/ssh"
)
// HandleRequest handles a request to sign an SSH public key verified by an OpenIDConnect id_token
func HandleRequest(ctx context.Context, event signer.SSHrimpEvent) (*signer.SSHrimpResult, error) {
// Make sure we are running in a lambda context, to get the requestid and ARN
lambdaContext, ok := lambdacontext.FromContext(ctx)
if !ok {
return nil, fmt.Errorf("lambdacontext not in ctx")
}
// Load the configuration file, if not exsits, exit.
c := config.NewSSHrimp()
if err := c.Read(config.GetPath()); err != nil {
return nil, err
}
// Validate the user supplied public key
publicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(event.PublicKey))
if err != nil {
return nil, fmt.Errorf("unable to parse public key: %v", err)
}
// Validate the user supplied identity token with the loaded configuration
i, err := identity.NewIdentity(c)
username, err := i.Validate(event.Token)
if err != nil {
return nil, err
}
// Validate and add force commands or source address options
criticalOptions := make(map[string]string)
if regexp.MustCompile(c.CertificateAuthority.ForceCommandRegex).MatchString(event.ForceCommand) {
if event.ForceCommand != "" {
criticalOptions["force-command"] = event.ForceCommand
}
} else {
return nil, errors.New("forcecommand validation failed")
}
if regexp.MustCompile(c.CertificateAuthority.SourceAddressRegex).MatchString(event.SourceAddress) {
if event.SourceAddress != "" {
criticalOptions["source-address"] = event.SourceAddress
}
} else {
return nil, errors.New("sourceaddress validation failed")
}
// Generate a random nonce for the certificate
bytes := make([]byte, 32)
nonce := make([]byte, len(bytes)*2)
if _, err := rand.Read(bytes); err != nil {
return nil, err
}
hex.Encode(nonce, bytes)
// Generate a random serial number
serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
return nil, err
}
// Validate and set the certificate valid and expire times
now := time.Now()
validAfterOffset, err := time.ParseDuration(c.CertificateAuthority.ValidAfterOffset)
if err != nil {
return nil, err
}
validBeforeOffset, err := time.ParseDuration(c.CertificateAuthority.ValidBeforeOffset)
if err != nil {
return nil, err
}
validAfter := now.Add(validAfterOffset)
validBefore := now.Add(validBeforeOffset)
// Convert the extensions slice to a map
extensions := make(map[string]string, len(c.CertificateAuthority.Extensions))
for _, extension := range c.CertificateAuthority.Extensions {
extensions[extension] = ""
}
// Create a key ID to be added to the certificate. Follows BLESS Key ID format
// https://github.com/Netflix/bless
keyID := fmt.Sprintf("request[%s] for[%s] from[%s] command[%s] ssh_key[%s] ca[%s] valid_to[%s]",
lambdaContext.AwsRequestID,
username,
event.SourceAddress,
event.ForceCommand,
ssh.FingerprintSHA256(publicKey),
lambdaContext.InvokedFunctionArn,
validBefore.Format("2006/01/02 15:04:05"),
)
// Create the certificate struct with all our configured alues
certificate := ssh.Certificate{
Nonce: nonce,
Key: publicKey,
Serial: serial.Uint64(),
CertType: ssh.UserCert,
KeyId: keyID,
ValidPrincipals: []string{
username,
},
Permissions: ssh.Permissions{
CriticalOptions: criticalOptions,
Extensions: extensions,
},
ValidAfter: uint64(validAfter.Unix()),
ValidBefore: uint64(validBefore.Unix()),
}
// Setup our Certificate Authority signer backed by KMS
kmsSigner := signer.NewKMSSigner(c.CertificateAuthority.KeyAlias)
sshAlgorithmSigner, err := signer.NewAlgorithmSignerFromSigner(kmsSigner, ssh.SigAlgoRSASHA2256)
if err != nil {
return nil, err
}
// Sign the certificate!!
if err := certificate.SignCert(rand.Reader, sshAlgorithmSigner); err != nil {
return nil, err
}
// Extract the public key (certificate) to return to the user
pubkey, err := ssh.ParsePublicKey(certificate.Marshal())
if err != nil {
return nil, err
}
// Success!
return &signer.SSHrimpResult{
Certificate: string(ssh.MarshalAuthorizedKey(pubkey)),
ErrorMessage: "",
ErrorType: "",
}, nil
}
func main() {
lambda.Start(HandleRequest)
}