sshrimp/internal/signer/sshrimp.go
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

93 lines
3.0 KiB
Go

package signer
import (
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/pkg/errors"
"github.com/stoggi/sshrimp/internal/config"
"golang.org/x/crypto/ssh"
)
// SSHrimpResult encodes the payload format returned from the sshrimp-ca lambda
type SSHrimpResult struct {
Certificate string `json:"certificate"`
ErrorMessage string `json:"errorMessage"`
ErrorType string `json:"errorType"`
}
// SSHrimpEvent encodes the user input for the sshrimp-ca lambda
type SSHrimpEvent struct {
PublicKey string `json:"publickey"`
Token string `json:"token"`
SourceAddress string `json:"sourceaddress"`
ForceCommand string `json:"forcecommand"`
}
// SignCertificateAllRegions iterate through each configured region if there is an error signing the certificate
func SignCertificateAllRegions(publicKey ssh.PublicKey, token string, forceCommand string, c *config.SSHrimp) (*ssh.Certificate, error) {
var err error
// Try each configured region before exiting if there is an error
for _, region := range c.CertificateAuthority.Regions {
cert, err := SignCertificateOneRegion(publicKey, token, forceCommand, region, c)
if err == nil {
return cert, nil
}
}
return nil, err
}
// SignCertificateOneRegion given a public key, identity token and forceCommand, invoke the sshrimp-ca lambda function
func SignCertificateOneRegion(publicKey ssh.PublicKey, token string, forceCommand string, region string, c *config.SSHrimp) (*ssh.Certificate, error) {
// Create a lambdaService using the new temporary credentials for the role
session := session.Must(session.NewSession(&aws.Config{
Region: aws.String(region),
}))
lambdaService := lambda.New(session)
// Setup the JSON payload for the SSHrimp CA
payload, err := json.Marshal(SSHrimpEvent{
PublicKey: string(ssh.MarshalAuthorizedKey(publicKey)),
Token: token,
ForceCommand: forceCommand,
})
if err != nil {
return nil, err
}
// Invoke the SSHrimp lambda
result, err := lambdaService.Invoke(&lambda.InvokeInput{
FunctionName: aws.String(c.CertificateAuthority.FunctionName),
Payload: payload,
})
if err != nil {
return nil, err
}
if *result.StatusCode != 200 {
return nil, fmt.Errorf("sshrimp returned status code %d", *result.StatusCode)
}
// Parse the result form the lambda to extract the certificate
sshrimpResult := SSHrimpResult{}
err = json.Unmarshal(result.Payload, &sshrimpResult)
if err != nil {
return nil, errors.Wrap(err, "failed to parse json response from sshrimp-ca")
}
// These error types and messages can also come from the aws-sdk-go lambda handler
if sshrimpResult.ErrorType != "" || sshrimpResult.ErrorMessage != "" {
return nil, fmt.Errorf("%s: %s", sshrimpResult.ErrorType, sshrimpResult.ErrorMessage)
}
// Parse the certificate received by sshrimp-ca
cert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshrimpResult.Certificate))
if err != nil {
return nil, err
}
return cert.(*ssh.Certificate), nil
}