* 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.
159 lines
4.5 KiB
Go
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)
|
|
}
|