Add initial support for GCP
I've never used cloud SDK's before so no terraform files. To make this work I had to use the Go 1.11 runtime as the go 1.13 runtime did not have the correct environment variables set. I also had to use the key with PKCS#1 v1.5 padding the PSS Padding did not work.
This commit is contained in:
parent
6c3e3ad912
commit
04122127c2
@ -3,18 +3,11 @@ 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"
|
||||
)
|
||||
@ -34,101 +27,14 @@ func HandleRequest(ctx context.Context, event signer.SSHrimpEvent) (*signer.SSHr
|
||||
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)
|
||||
// Create the certificate struct with all our configured values
|
||||
certificate, err := signer.ValidateRequest(event, c, lambdaContext.AwsRequestID, lambdaContext.InvokedFunctionArn)
|
||||
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)
|
||||
kmsSigner := signer.NewAWSSigner(c.CertificateAuthority.KeyAlias)
|
||||
sshAlgorithmSigner, err := signer.NewAlgorithmSignerFromSigner(kmsSigner, ssh.SigAlgoRSASHA2256)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
77
gcp/gcp.go
Normal file
77
gcp/gcp.go
Normal file
@ -0,0 +1,77 @@
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/stoggi/sshrimp/internal/config"
|
||||
"github.com/stoggi/sshrimp/internal/signer"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func httpError(w http.ResponseWriter, v interface{}, statusCode int) {
|
||||
e := json.NewEncoder(w)
|
||||
err := e.Encode(v)
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
}
|
||||
|
||||
// HandleRequest handles a request to sign an SSH public key verified by an OpenIDConnect id_token
|
||||
func SSHrimp(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Load the configuration file, if not exsits, exit.
|
||||
c := config.NewSSHrimp()
|
||||
if err := c.Read(config.GetPath()); err != nil {
|
||||
httpError(w, signer.SSHrimpResult{"", err.Error(), http.StatusText(http.StatusInternalServerError)}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var event signer.SSHrimpEvent
|
||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||
httpError(w, signer.SSHrimpResult{"", err.Error(), http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
certificate, err := signer.ValidateRequest(event, c, r.Header.Get("Function-Execution-Id"), fmt.Sprintf("%s/%s/%s", os.Getenv("GCP_PROJECT"), os.Getenv("FUNCTION_REGION"), os.Getenv("FUNCTION_NAME")))
|
||||
if err != nil {
|
||||
httpError(w, signer.SSHrimpResult{"", err.Error(), http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Setup our Certificate Authority signer backed by KMS
|
||||
kmsSigner := signer.NewGCPSSigner(c.CertificateAuthority.KeyAlias)
|
||||
|
||||
sshAlgorithmSigner, err := signer.NewAlgorithmSignerFromSigner(kmsSigner, ssh.SigAlgoRSASHA2256)
|
||||
if err != nil {
|
||||
httpError(w, signer.SSHrimpResult{"", err.Error(), http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign the certificate!!
|
||||
if err := certificate.SignCert(rand.Reader, sshAlgorithmSigner); err != nil {
|
||||
httpError(w, signer.SSHrimpResult{"", err.Error(), http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the public key (certificate) to return to the user
|
||||
pubkey, err := ssh.ParsePublicKey(certificate.Marshal())
|
||||
if err != nil {
|
||||
httpError(w, signer.SSHrimpResult{"", err.Error(), http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Success!
|
||||
res := &signer.SSHrimpResult{
|
||||
Certificate: string(ssh.MarshalAuthorizedKey(pubkey)),
|
||||
ErrorMessage: "",
|
||||
ErrorType: "",
|
||||
}
|
||||
e := json.NewEncoder(w)
|
||||
err = e.Encode(res)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
5
go.mod
5
go.mod
@ -5,6 +5,7 @@ go 1.13
|
||||
replace github.com/b-b3rn4rd/gocfn => github.com/stoggi/gocfn v0.0.0-20200214083946-6202cea979b9
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.38.0
|
||||
github.com/AlecAivazis/survey/v2 v2.0.5
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/alecthomas/colour v0.1.0 // indirect
|
||||
@ -17,6 +18,7 @@ require (
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base v0.4.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/magefile/mage v1.9.0
|
||||
@ -28,6 +30,9 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
|
||||
google.golang.org/api v0.21.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200413115906-b5235f65be36
|
||||
google.golang.org/grpc v1.28.1 // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
)
|
||||
|
80
go.sum
80
go.sum
@ -1,4 +1,8 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/99designs/aws-vault v4.5.1+incompatible/go.mod h1:BKt7gBiUkiAOh7TP/c36gMpRJkIk5F8hStyQoWwC/Rw=
|
||||
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f h1:WXiWWJrYCaOaYimBAXlRdRJ7qOisrYyMLYnCvvhHVms=
|
||||
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f/go.mod h1:aKt8W/yd91/xHY6ixZAJZ2vYbhr3pP8DcrvuGSGNPJk=
|
||||
@ -32,10 +36,13 @@ github.com/awslabs/goformation/v4 v4.4.0 h1:MwHqnYX+ebauk3EmQX8WXirI8OmHg/Cg34dt
|
||||
github.com/awslabs/goformation/v4 v4.4.0/go.mod h1:MBDN7u1lMNDoehbFuO4uPvgwPeolTMA2TzX1yO6KlxI=
|
||||
github.com/b-b3rn4rd/gocfn v0.0.0-20180729083956-9f400ac88956 h1:AR0vL4FEL1H4yhjXYtz8RJZ5xWO1CBs8IladWiCV3aU=
|
||||
github.com/b-b3rn4rd/gocfn v0.0.0-20180729083956-9f400ac88956/go.mod h1:kOCOw4KbqX3dYcTLjMneEELuPQ9drjIRtGMpwqhBrak=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
@ -47,16 +54,36 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU=
|
||||
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.4.0 h1:zH9hNUdsS+2G0zJaU85ul8D59BGnZBaKM+KMNPAHGwk=
|
||||
@ -67,12 +94,16 @@ github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6K
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
@ -120,6 +151,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
|
||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
|
||||
@ -149,26 +181,43 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8=
|
||||
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20170809000501-1c05540f6879/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170814044513-c84c1ab9fd18/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -176,15 +225,43 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170814122439-e56139fd9c5b/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY=
|
||||
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200413115906-b5235f65be36 h1:j7CmVRD4Kec0+f8VuBAc2Ak2MFfXm5Q2/RxuJLL+76E=
|
||||
google.golang.org/genproto v0.0.0-20200413115906-b5235f65be36/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k=
|
||||
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -198,3 +275,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -26,6 +26,7 @@ type Agent struct {
|
||||
|
||||
// CertificateAuthority config for the sshrimp-ca lambda
|
||||
type CertificateAuthority struct {
|
||||
Project string
|
||||
AccountID int
|
||||
Regions []string
|
||||
FunctionName string
|
||||
@ -46,7 +47,7 @@ type SSHrimp struct {
|
||||
}
|
||||
|
||||
// List of supported regions for the config wizard
|
||||
var supportedAwsRegions = []string{
|
||||
var SupportedAwsRegions = []string{
|
||||
"ap-east-1",
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
@ -67,6 +68,17 @@ var supportedAwsRegions = []string{
|
||||
"us-west-2",
|
||||
}
|
||||
|
||||
var SupportedGcpRegions = []string{
|
||||
"europe-west1",
|
||||
"europe-west2",
|
||||
"europe-west3",
|
||||
"us-central1",
|
||||
"us-east1",
|
||||
"us-east4",
|
||||
"asia-northeast1",
|
||||
"asia-east2",
|
||||
}
|
||||
|
||||
var supportedExtensions = []string{
|
||||
"no-agent-forwarding",
|
||||
"no-port-forwarding",
|
||||
@ -202,7 +214,7 @@ func certificateAuthorityQuestions(config *SSHrimp) []*survey.Question {
|
||||
Message: "AWS Region:",
|
||||
Default: config.CertificateAuthority.Regions,
|
||||
Help: "Select multiple regions for high availability. Each region gets it's own Lambda function and KMS key.",
|
||||
Options: supportedAwsRegions,
|
||||
Options: SupportedAwsRegions,
|
||||
PageSize: 10,
|
||||
},
|
||||
Validate: survey.Required,
|
||||
|
@ -13,25 +13,25 @@ import (
|
||||
)
|
||||
|
||||
// KMSSigner an AWS asymetric crypto signer
|
||||
type KMSSigner struct {
|
||||
type AWSSigner struct {
|
||||
crypto.Signer
|
||||
client kmsiface.KMSAPI
|
||||
key string
|
||||
}
|
||||
|
||||
// NewKMSSigner return a new instsance of KMSSigner
|
||||
func NewKMSSigner(key string) *KMSSigner {
|
||||
// NewKMSSigner return a new instsance of AWSSigner
|
||||
func NewAWSSigner(key string) *AWSSigner {
|
||||
|
||||
sess := session.Must(session.NewSession())
|
||||
|
||||
return &KMSSigner{
|
||||
return &AWSSigner{
|
||||
key: key,
|
||||
client: kms.New(sess),
|
||||
}
|
||||
}
|
||||
|
||||
// Public returns the public key from KMS
|
||||
func (s *KMSSigner) Public() crypto.PublicKey {
|
||||
func (s *AWSSigner) Public() crypto.PublicKey {
|
||||
|
||||
response, err := s.client.GetPublicKey(&kms.GetPublicKeyInput{
|
||||
KeyId: &s.key,
|
||||
@ -51,7 +51,7 @@ func (s *KMSSigner) Public() crypto.PublicKey {
|
||||
}
|
||||
|
||||
// Sign a digest with the private key in KMS
|
||||
func (s *KMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
func (s *AWSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
|
||||
response, err := s.client.Sign(&kms.SignInput{
|
||||
KeyId: &s.key,
|
89
internal/signer/gcp.go
Normal file
89
internal/signer/gcp.go
Normal file
@ -0,0 +1,89 @@
|
||||
package signer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
kms "cloud.google.com/go/kms/apiv1"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
)
|
||||
|
||||
// KMSSigner a GCP asymetric crypto signer
|
||||
type GCPSigner struct {
|
||||
crypto.Signer
|
||||
ctx context.Context
|
||||
client *kms.KeyManagementClient
|
||||
key string
|
||||
}
|
||||
|
||||
// NewGCPSSigner return a new instsance of NewGCPSSigner
|
||||
func NewGCPSSigner(key string) *GCPSigner {
|
||||
ctx := context.Background()
|
||||
c, err := kms.NewKeyManagementClient(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &GCPSigner{
|
||||
ctx: ctx,
|
||||
client: c,
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// Public returns the public key from KMS
|
||||
func (s *GCPSigner) Public() crypto.PublicKey {
|
||||
|
||||
response, err := s.client.GetPublicKey(s.ctx, &kmspb.GetPublicKeyRequest{
|
||||
Name: s.key,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
pubPem := response.GetPem()
|
||||
// pubAlg := response.GetAlgorithm()
|
||||
pemBlock, _ := pem.Decode([]byte(pubPem))
|
||||
|
||||
publicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return publicKey
|
||||
}
|
||||
|
||||
// Sign a digest with the private key in KMS
|
||||
func (s *GCPSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
var dig *kmspb.Digest = &kmspb.Digest{}
|
||||
switch opts {
|
||||
case crypto.SHA256:
|
||||
dig.Digest = &kmspb.Digest_Sha256{
|
||||
Sha256: digest,
|
||||
}
|
||||
case crypto.SHA384:
|
||||
dig.Digest = &kmspb.Digest_Sha384{
|
||||
Sha384: digest,
|
||||
}
|
||||
case crypto.SHA512:
|
||||
dig.Digest = &kmspb.Digest_Sha512{
|
||||
Sha512: digest,
|
||||
}
|
||||
}
|
||||
|
||||
response, err := s.client.AsymmetricSign(s.ctx, &kmspb.AsymmetricSignRequest{
|
||||
Name: s.key,
|
||||
Digest: dig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.GetSignature(), nil
|
||||
}
|
@ -1,14 +1,26 @@
|
||||
package signer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/stoggi/sshrimp/internal/identity"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@ -29,11 +41,19 @@ type SSHrimpEvent struct {
|
||||
|
||||
// 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
|
||||
var (
|
||||
err error
|
||||
cert *ssh.Certificate
|
||||
)
|
||||
|
||||
// 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 i := sort.SearchStrings(config.SupportedAwsRegions, region); i < len(config.SupportedAwsRegions) && config.SupportedAwsRegions[i] == region {
|
||||
cert, err = SignCertificateAWS(publicKey, token, forceCommand, region, c)
|
||||
} else if i := sort.SearchStrings(config.SupportedGcpRegions, region); i < len(config.SupportedGcpRegions) && config.SupportedGcpRegions[i] == region {
|
||||
cert, err = SignCertificateGCP(publicKey, token, forceCommand, region, c)
|
||||
}
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
@ -41,8 +61,54 @@ func SignCertificateAllRegions(publicKey ssh.PublicKey, token string, forceComma
|
||||
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) {
|
||||
// SignCertificateGCP given a public key, identity token and forceCommand, invoke the sshrimp-ca GCP function
|
||||
func SignCertificateGCP(publicKey ssh.PublicKey, token string, forceCommand string, region string, c *config.SSHrimp) (*ssh.Certificate, error) {
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
result, err := http.Post(fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", region, c.CertificateAuthority.Project, c.CertificateAuthority.FunctionName), "application/json", bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "http post failed: "+err.Error())
|
||||
}
|
||||
if result.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("sshrimp returned status code %d", result.StatusCode)
|
||||
}
|
||||
|
||||
resbody, err := ioutil.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to retrieve the response from sshrimp-ca")
|
||||
}
|
||||
|
||||
// Parse the result form the lambda to extract the certificate
|
||||
sshrimpResult := SSHrimpResult{}
|
||||
err = json.Unmarshal(resbody, &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
|
||||
}
|
||||
|
||||
// SignCertificateAWS given a public key, identity token and forceCommand, invoke the sshrimp-ca lambda function
|
||||
func SignCertificateAWS(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),
|
||||
@ -90,3 +156,99 @@ func SignCertificateOneRegion(publicKey ssh.PublicKey, token string, forceComman
|
||||
}
|
||||
return cert.(*ssh.Certificate), nil
|
||||
}
|
||||
|
||||
func ValidateRequest(event SSHrimpEvent, c *config.SSHrimp, requestID string, functionID string) (ssh.Certificate, error) {
|
||||
// Validate the user supplied public key
|
||||
publicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(event.PublicKey))
|
||||
if err != nil {
|
||||
return ssh.Certificate{}, 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 ssh.Certificate{}, 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 ssh.Certificate{}, errors.New("forcecommand validation failed")
|
||||
}
|
||||
if regexp.MustCompile(c.CertificateAuthority.SourceAddressRegex).MatchString(event.SourceAddress) {
|
||||
if event.SourceAddress != "" {
|
||||
criticalOptions["source-address"] = event.SourceAddress
|
||||
}
|
||||
} else {
|
||||
return ssh.Certificate{}, 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 ssh.Certificate{}, err
|
||||
}
|
||||
hex.Encode(nonce, bytes)
|
||||
|
||||
// Generate a random serial number
|
||||
serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||
if err != nil {
|
||||
return ssh.Certificate{}, err
|
||||
}
|
||||
|
||||
// Validate and set the certificate valid and expire times
|
||||
now := time.Now()
|
||||
validAfterOffset, err := time.ParseDuration(c.CertificateAuthority.ValidAfterOffset)
|
||||
if err != nil {
|
||||
return ssh.Certificate{}, err
|
||||
}
|
||||
validBeforeOffset, err := time.ParseDuration(c.CertificateAuthority.ValidBeforeOffset)
|
||||
if err != nil {
|
||||
return ssh.Certificate{}, 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]",
|
||||
requestID,
|
||||
username,
|
||||
event.SourceAddress,
|
||||
event.ForceCommand,
|
||||
ssh.FingerprintSHA256(publicKey),
|
||||
functionID,
|
||||
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()),
|
||||
}
|
||||
return certificate, nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
@ -42,6 +43,15 @@ func Build() error {
|
||||
return sh.RunWith(env, "go", "build", "./cmd/sshrimp-ca")
|
||||
}
|
||||
|
||||
// Build Builds the GCP certificate authority
|
||||
// It produces gcp.a for checking timestamps
|
||||
func BuildGCP() error {
|
||||
env := map[string]string{
|
||||
"GOOS": "linux",
|
||||
}
|
||||
return sh.RunWith(env, "go", "build", "-o", "gcp.a", "./gcp")
|
||||
}
|
||||
|
||||
// Package Packages the certificate authority files into a zip archive
|
||||
func Package() error {
|
||||
if modified, err := target.Path("sshrimp-ca", config.GetPath()); err == nil && !modified {
|
||||
@ -62,6 +72,33 @@ func Package() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageGCP Packages the certificate authority files into a zip archive
|
||||
func PackageGCP() error {
|
||||
if modified, err := target.Path("gcp.a", config.GetPath()); err == nil && !modified {
|
||||
return nil
|
||||
}
|
||||
|
||||
mg.Deps(BuildGCP, Config)
|
||||
|
||||
zipFile, err := os.Create("sshrimp-ca-gcp.zip")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
err = gcpCreateArchive(zipFile, []ZipFiles{
|
||||
ZipFiles{Filename: "go.mod"},
|
||||
{"gcp/gcp.go", "gcp.go"},
|
||||
ZipFiles{Filename: "internal"},
|
||||
ZipFiles{config.GetPath(), filepath.Base(config.GetPath())},
|
||||
}...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate Generates a CloudFormation template used to deploy the certificate authority
|
||||
func Generate() error {
|
||||
if modified, err := target.Path("sshrimp-ca.tf.json", config.GetPath()); err == nil && !modified {
|
||||
@ -139,5 +176,11 @@ func Clean() error {
|
||||
if err := sh.Rm("sshrimp-ca.zip"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sh.Rm("sshrimp-ca-gcp.zip"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sh.Rm("gcp.a"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -4,8 +4,14 @@ import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ZipFiles struct {
|
||||
Filename, ZipPath string
|
||||
}
|
||||
|
||||
// Packages the certificate authority lambda into a zip archive on writer
|
||||
func lambdaCreateArchive(wr io.Writer, filename ...string) error {
|
||||
|
||||
@ -41,3 +47,90 @@ func lambdaCreateArchive(wr io.Writer, filename ...string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Packages the certificate authority function into a GCP compatible zip archive on writer
|
||||
func gcpCreateArchive(wr io.Writer, files ...ZipFiles) error {
|
||||
|
||||
archive := zip.NewWriter(wr)
|
||||
defer archive.Close()
|
||||
|
||||
for _, fileinfo := range files {
|
||||
info, err := os.Stat(fileinfo.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
err = zipDirectory(archive, fileinfo.Filename, fileinfo.ZipPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.TrimSpace(fileinfo.ZipPath) == "" {
|
||||
fileinfo.ZipPath = fileinfo.Filename
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = fileinfo.ZipPath
|
||||
|
||||
writer, err := archive.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(fileinfo.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(writer, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func zipDirectory(archive *zip.Writer, dir, zipDir string) error {
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var zipPath string
|
||||
if strings.TrimSpace(zipDir) == "" {
|
||||
zipPath = path
|
||||
} else {
|
||||
zipPath = strings.Replace(path, dir, zipDir, 1)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = zipPath
|
||||
|
||||
writer, err := archive.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(writer, file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user