Skip to content

Commit

Permalink
feat(Bless) It works! 🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfeidau committed Sep 27, 2017
0 parents commit 0163c9c
Show file tree
Hide file tree
Showing 9 changed files with 2,652 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
coverage.txt
dist
11 changes: 11 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

build:
main: cmd/versent-bless/main.go
binary: versent-bless
goos:
- darwin
- linux
- windows
goarch:
- amd64
- 386
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2017 Versent Pty. Ltd.

Please consider promoting this project if you find it useful.

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 changes: 44 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
SOURCE_FILES?=$$(go list ./... | grep -v /vendor/)
TEST_PATTERN?=.
TEST_OPTIONS?=

GO ?= go

# Install all the build and lint dependencies
setup:
go get -u github.com/alecthomas/gometalinter
go get -u github.com/golang/dep/cmd/dep
go get -u github.com/pierrre/gotestcover
go get -u golang.org/x/tools/cmd/cover
go get github.com/vektra/mockery/...
gometalinter --install
.PHONY: setup

# Install from source.
install:
@echo "==> Installing up ${GOPATH}/bin/versent-bless"
@$(GO) install ./...
.PHONY: install

# Run all the tests
test:
@gotestcover $(TEST_OPTIONS) -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
.PHONY: test

# Run all the tests and opens the coverage report
cover: test
@$(GO) tool cover -html=coverage.txt
.PHONY: cover

# Release binaries to GitHub.
release:
@echo "==> Releasing"
@goreleaser -p 1 --rm-dist -config .goreleaser.yml
@echo "==> Complete"
.PHONY: release

generate-mocks:
mockery -dir ../../aws/aws-sdk-go/service/lambda/lambdaiface --all
.PHONY: generate-mocks

.PHONY: setup test cover generate-mocks
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# versent bless client

This project is a port of the [netflix/bless/bless_client](https://github.com/Netflix/bless/tree/master/bless_client).

# usage

```
usage: versent-bless [<flags>] <command> [<args> ...]
A command line client for netflix bless.
Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--debug Enable debug mode.
Commands:
help [<command>...]
Show help.
login <region> <lambda_function_name> <bastion_user> <bastion_user_ip> <remote_usernames> <bastion_ips> <bastion_command> <public_key_to_sign> <certificate_filename> [<kmsauth_token>]
Login and retrieve a key.
```

# license

This code is released under MIT License.
113 changes: 113 additions & 0 deletions bless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package bless

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/aws/aws-sdk-go/service/lambda/lambdaiface"

"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"
)

var (
lambdaSvc lambdaiface.LambdaAPI = lambda.New(session.New())

debug = false
)

// Payload used to encode request payload for bless lambda
type Payload struct {
BastionUser string `json:"bastion_user"`
BastionUserIP string `json:"bastion_user_ip"`
RemoteUsernames string `json:"remote_usernames"`
BastionIps string `json:"bastion_ips"`
BastionCommand string `json:"command"`
PublicKeyToSign string `json:"public_key_to_sign"`
KmsAuthToken string `json:"kms_auth_token,omitempty"`
}

// Result used to decode response from bless lambda
type Result struct {
Certificate string `json:"certificate,omitempty"`
}

// ConfigureAws enable override of the default aws sdk configuration
func ConfigureAws(config *aws.Config) {
lambdaSvc = lambda.New(session.New(config))
}

// SetDebug enable or disable debugging
func SetDebug(enable bool) {
debug = enable
}

// LoadPublicKey load the public key from the supplied path
func LoadPublicKey(publicKey string) ([]byte, error) {
data, err := ioutil.ReadFile(publicKey)
if err != nil {
return nil, errors.Wrap(err, "Failed to load public key")
}

return data, nil
}

// ValidatePublicKey validate the public key
func ValidatePublicKey(publicKeyData []byte) (string, error) {
if len(publicKeyData) == 0 {
return "", errors.New("Empty public key supplied")
}

return string(publicKeyData), nil
}

// InvokeBlessLambda invoke the bless lambda function
func InvokeBlessLambda(region, lambdaFunctionName *string, payloadJSON []byte) (*Result, error) {

input := &lambda.InvokeInput{
FunctionName: lambdaFunctionName,
InvocationType: aws.String("RequestResponse"),
LogType: aws.String("None"),
Payload: payloadJSON,
}

res, err := lambdaSvc.Invoke(input)
if err != nil {
return nil, errors.Wrap(err, "Lambda invoke failed")
}

if aws.Int64Value(res.StatusCode) != 200 {
return nil, errors.Wrapf(err, "Lambda Invoke failed: %v", res)
}

resultPayload := &Result{}

err = json.Unmarshal(res.Payload, resultPayload)
if err != nil {
return nil, errors.Wrap(err, "Failed to deserialise JSON result")
}

return resultPayload, nil
}

// WriteCertificate write the generated certificate out to a file
func WriteCertificate(certificateFilename string, certificateContent string) error {
err := ioutil.WriteFile(certificateFilename, bytes.NewBufferString(certificateContent).Bytes(), 0600)
if err != nil {
return errors.Wrap(err, "Failed to write certificate file")
}

return nil
}

// Debug write debug messages if it is enabled
func Debug(message string, args ...interface{}) {
if debug {
fmt.Fprintf(os.Stderr, message, args...)
}
}
78 changes: 78 additions & 0 deletions bless_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package bless

import (
"fmt"
"io/ioutil"
"os"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/lambda"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/versent/bless/mocks"
)

func TestLoadPublicKey(t *testing.T) {

tfile := mustWriteTempfile("data")

data, err := LoadPublicKey(tfile.Name())
require.Nil(t, err)

require.Len(t, data, 4)

_, err = LoadPublicKey("badfile")
require.Error(t, err)

}

func TestValidatePublicKey(t *testing.T) {
data, err := ValidatePublicKey([]byte{0xa, 0xb, 0xc, 0xd})
require.Nil(t, err)
require.Len(t, data, 4)

_, err = ValidatePublicKey([]byte{})
require.Error(t, err)
}

func TestWriteCertificate(t *testing.T) {
err := WriteCertificate(fmt.Sprintf("%s/%s-%d", os.TempDir(), "abc", time.Now().Unix()), "data")
require.Nil(t, err)
err = WriteCertificate(fmt.Sprintf("nothere/%s-%d", "abc", time.Now().Unix()), "data")
require.Error(t, err)
}

func TestInvokeBlessLambda(t *testing.T) {

lambdaMock := &mocks.LambdaAPI{}

lambdaSvc = lambdaMock

result := &lambda.InvokeOutput{
StatusCode: aws.Int64(200),
Payload: []byte(`{"certificate":"data"}`),
}

lambdaMock.On("Invoke", mock.AnythingOfType("*lambda.InvokeInput")).Return(result, nil)

res, err := InvokeBlessLambda(aws.String("us-west-2"), aws.String("whatever"), []byte("whatever"))
require.Nil(t, err)
require.Len(t, res.Certificate, 4)
}

func mustWriteTempfile(data string) *os.File {
tfile, err := ioutil.TempFile(os.TempDir(), "test")
if err != nil {
panic(err)
}

_, err = tfile.WriteString("data")
if err != nil {
panic(err)
}

return tfile
}
88 changes: 88 additions & 0 deletions cmd/versent-bless/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package main

import (
"encoding/json"
"log"
"os"

"github.com/alecthomas/kingpin"
"github.com/aws/aws-sdk-go/aws"
"github.com/versent/bless"
)

var (
app = kingpin.New("versent-bless", "A command line client for netflix bless.")
debug = app.Flag("debug", "Enable debug mode.").Bool()
login = app.Command("login", "Login and retrieve a key.")

region = login.Arg("region", "AWS Region.").Required().String()
lambdaFunctionName = login.Arg("lambda_function_name", "Lambda function name.").Required().String()
bastionUser = login.Arg("bastion_user", "Bastion user.").Required().String()
bastionUserIP = login.Arg("bastion_user_ip", "Bastion user IP.").Required().String()
remoteUsernames = login.Arg("remote_usernames", "Remote user names.").Required().String()
bastionIps = login.Arg("bastion_ips", "Bastion IPs.").Required().String()
bastionCommand = login.Arg("bastion_command", "Bastion command.").Required().String()
publicKeyToSign = login.Arg("public_key_to_sign", "Public key to sign.").Required().String()
certificateFilename = login.Arg("certificate_filename", "Certificate filename.").Required().String()
kmsAuthToken = login.Arg("kmsauth_token", "KMS Auth Token.").String()
)

func main() {

switch kingpin.MustParse(app.Parse(os.Args[1:])) {
case login.FullCommand():

bless.ConfigureAws(&aws.Config{Region: region})

bless.SetDebug(*debug)

publicKeyData, err := bless.LoadPublicKey(*publicKeyToSign)
if err != nil {
log.Fatalf("%+v\n", err)
}

bless.Debug("publicKeyData: %s", string(publicKeyData))

publicKey, err := bless.ValidatePublicKey(publicKeyData)
if err != nil {
log.Fatalf("%+v\n", err)
}

payload := &bless.Payload{
BastionUser: *bastionUser,
BastionUserIP: *bastionUserIP,
RemoteUsernames: *remoteUsernames,
BastionIps: *bastionIps,
BastionCommand: *bastionCommand,
PublicKeyToSign: publicKey,
}

if kmsAuthToken != nil {
payload.KmsAuthToken = *kmsAuthToken
}

bless.Debug("payload: %v", payload)

payloadJSON, err := json.Marshal(payload)
if err != nil {
log.Fatalf("%+v\n", err)
}

log.Printf("payload_json is: %s", string(payloadJSON))

resultPayload, err := bless.InvokeBlessLambda(region, lambdaFunctionName, payloadJSON)
if err != nil {
log.Fatalf("%+v\n", err)
}

bless.Debug("resultPayload: %v", resultPayload)

err = bless.WriteCertificate(*certificateFilename, resultPayload.Certificate)
if err != nil {
log.Fatalf("%+v\n", err)
}

log.Printf("Wrote Certificate to: %s", *certificateFilename)

}
}
Loading

0 comments on commit 0163c9c

Please sign in to comment.