builder/triton: Add support for Triton RBAC

Fixes: #5737

Triton allows for subusers to be granted access to your account. This PR allows a user to be specified
This commit is contained in:
stack72 2017-12-29 15:00:47 +02:00
parent 4e1beb009c
commit c3467b686c
21 changed files with 854 additions and 134 deletions

View File

@ -19,6 +19,7 @@ import (
type AccessConfig struct {
Endpoint string `mapstructure:"triton_url"`
Account string `mapstructure:"triton_account"`
Username string `mapstructure:"triton_user"`
KeyID string `mapstructure:"triton_key_id"`
KeyMaterial string `mapstructure:"triton_key_material"`
@ -65,7 +66,12 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
}
func (c *AccessConfig) createSSHAgentSigner() (authentication.Signer, error) {
signer, err := authentication.NewSSHAgentSigner(c.KeyID, c.Account)
input := authentication.SSHAgentSignerInput{
KeyID: c.KeyID,
AccountName: c.Account,
Username: c.Username,
}
signer, err := authentication.NewSSHAgentSigner(input)
if err != nil {
return nil, fmt.Errorf("Error creating Triton request signer: %s", err)
}
@ -94,8 +100,14 @@ func (c *AccessConfig) createPrivateKeySigner() (authentication.Signer, error) {
}
}
// Create signer
signer, err := authentication.NewPrivateKeySigner(c.KeyID, privateKeyMaterial, c.Account)
input := authentication.PrivateKeySignerInput{
KeyID: c.KeyID,
AccountName: c.Account,
Username: c.Username,
PrivateKeyMaterial: privateKeyMaterial,
}
signer, err := authentication.NewPrivateKeySigner(input)
if err != nil {
return nil, fmt.Errorf("Error creating Triton request signer: %s", err)
}
@ -114,6 +126,7 @@ func (c *AccessConfig) CreateTritonClient() (*Client, error) {
config := &tgo.ClientConfig{
AccountName: c.Account,
TritonURL: c.Endpoint,
Username: c.Username,
Signers: []authentication.Signer{c.signer},
}

58
vendor/github.com/joyent/triton-go/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,58 @@
## Unreleased
## 0.5.2 (December 28)
- Standardise the API SSH Signers input casing and naming
## 0.5.1 (December 28)
- Include leading '/' when working with SSH Agent signers
## 0.5.0 (December 28)
- Add support for RBAC in triton-go [#82]
This is a breaking change. No longer do we pass individual parameters to the SSH Signer funcs, but we now pass an input Struct. This will guard from from additional parameter changes in the future.
We also now add support for using `SDC_*` and `TRITON_*` env vars when working with the Default agent signer
## 0.4.2 (December 22)
- Fixing a panic when the user loses network connectivity when making a GET request to instance [#81]
## 0.4.1 (December 15)
- Clean up the handling of directory sanitization. Use abs paths everywhere [#79]
## 0.4.0 (December 15)
- Fix an issue where Manta HEAD requests do not return an error resp body [#77]
- Add support for recursively creating child directories [#78]
## 0.3.0 (December 14)
- Introduce CloudAPI's ListRulesMachines under networking
- Enable HTTP KeepAlives by default in the client. 15s idle timeout, 2x
connections per host, total of 10x connections per client.
- Expose an optional Headers attribute to clients to allow them to customize
HTTP headers when making Object requests.
- Fix a bug in Directory ListIndex [#69](https://github.com/joyent/issues/69)
- Inputs to Object inputs have been relaxed to `io.Reader` (formerly a
`io.ReadSeeker`) [#73](https://github.com/joyent/issues/73).
- Add support for ForceDelete of all children of a directory [#71](https://github.com/joyent/issues/71)
- storage: Introduce `Objects.GetInfo` and `Objects.IsDir` using HEAD requests [#74](https://github.com/joyent/triton-go/issues/74)
## 0.2.1 (November 8)
- Fixing a bug where CreateUser and UpdateUser didn't return the UserID
## 0.2.0 (November 7)
- Introduce CloudAPI's Ping under compute
- Introduce CloudAPI's RebootMachine under compute instances
- Introduce CloudAPI's ListUsers, GetUser, CreateUser, UpdateUser and DeleteUser under identity package
- Introduce CloudAPI's ListMachineSnapshots, GetMachineSnapshot, CreateSnapshot, DeleteMachineSnapshot and StartMachineFromSnapshot under compute package
- tools: Introduce unit testing and scripts for linting, etc.
- bug: Fix the `compute.ListMachineRules` endpoint
## 0.1.0 (November 2)
- Initial release of a versioned SDK

47
vendor/github.com/joyent/triton-go/GNUmakefile generated vendored Normal file
View File

@ -0,0 +1,47 @@
TEST?=$$(go list ./... |grep -Ev 'vendor|examples|testutils')
GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
default: vet errcheck test
tools:: ## Download and install all dev/code tools
@echo "==> Installing dev tools"
go get -u github.com/golang/dep/cmd/dep
go get -u github.com/golang/lint/golint
go get -u github.com/kisielk/errcheck
@echo "==> Installing test package dependencies"
go test -i $(TEST) || exit 1
test:: ## Run unit tests
@echo "==> Running unit tests"
@echo $(TEST) | \
xargs -t go test -v $(TESTARGS) -timeout=30s -parallel=1 | grep -Ev 'TRITON_TEST|TestAcc'
testacc:: ## Run acceptance tests
@echo "==> Running acceptance tests"
TRITON_TEST=1 go test $(TEST) -v $(TESTARGS) -run -timeout 120m
vet:: ## Check for unwanted code constructs
@echo "go vet ."
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
echo ""; \
echo "Vet found suspicious constructs. Please check the reported constructs"; \
echo "and fix them if necessary before submitting the code for review."; \
exit 1; \
fi
lint:: ## Lint and vet code by common Go standards
@bash $(CURDIR)/scripts/lint.sh
fmt:: ## Format as canonical Go code
gofmt -w $(GOFMT_FILES)
fmtcheck:: ## Check if code format is canonical Go
@bash $(CURDIR)/scripts/gofmtcheck.sh
errcheck:: ## Check for unhandled errors
@bash $(CURDIR)/scripts/errcheck.sh
.PHONY: help
help:: ## Display this help message
@echo "GNU make(1) targets:"
@grep -E '^[a-zA-Z_.-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

39
vendor/github.com/joyent/triton-go/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,39 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/abdullin/seq"
packages = ["."]
revision = "d5467c17e7afe8d8f08f556c6c811a50c3feb28d"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/hashicorp/errwrap"
packages = ["."]
revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55"
[[projects]]
branch = "master"
name = "github.com/sean-/seed"
packages = ["."]
revision = "e2103e2c35297fb7e17febb81e49b312087a2372"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent"]
revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "28853a8970ee33112a9e7998b18e658bed04d177537ec69db678189f0b8a9a7d"
solver-name = "gps-cdcl"
solver-version = 1

42
vendor/github.com/joyent/triton-go/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,42 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/abdullin/seq"
[[constraint]]
name = "github.com/davecgh/go-spew"
version = "1.1.0"
[[constraint]]
branch = "master"
name = "github.com/hashicorp/errwrap"
[[constraint]]
branch = "master"
name = "github.com/sean-/seed"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"

View File

@ -3,6 +3,8 @@
`triton-go` is an idiomatic library exposing a client SDK for Go applications
using Joyent's Triton Compute and Storage (Manta) APIs.
[![Build Status](https://travis-ci.org/joyent/triton-go.svg?branch=master)](https://travis-ci.org/joyent/triton-go) [![Go Report Card](https://goreportcard.com/badge/github.com/joyent/triton-go)](https://goreportcard.com/report/github.com/joyent/triton-go)
## Usage
Triton uses [HTTP Signature][4] to sign the Date header in each HTTP request
@ -13,11 +15,17 @@ using a key stored with the local SSH Agent (using an [`SSHAgentSigner`][6].
To construct a Signer, use the `New*` range of methods in the `authentication`
package. In the case of `authentication.NewSSHAgentSigner`, the parameters are
the fingerprint of the key with which to sign, and the account name (normally
stored in the `SDC_ACCOUNT` environment variable). For example:
stored in the `TRITON_ACCOUNT` environment variable). There is also support for
passing in a username, this will allow you to use an account other than the main
Triton account. For example:
```
const fingerprint := "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11"
sshKeySigner, err := authentication.NewSSHAgentSigner(fingerprint, "AccountName")
```go
input := authentication.SSHAgentSignerInput{
KeyID: "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11",
AccountName: "AccountName",
Username: "Username",
}
sshKeySigner, err := authentication.NewSSHAgentSigner(input)
if err != nil {
log.Fatalf("NewSSHAgentSigner: %s", err)
}
@ -34,17 +42,18 @@ their own seperate client. In order to initialize a package client, simply pass
the global `triton.ClientConfig` struct into the client's constructor function.
```go
config := &triton.ClientConfig{
TritonURL: os.Getenv("SDC_URL"),
MantaURL: os.Getenv("MANTA_URL"),
AccountName: accountName,
Signers: []authentication.Signer{sshKeySigner},
}
config := &triton.ClientConfig{
TritonURL: os.Getenv("TRITON_URL"),
MantaURL: os.Getenv("MANTA_URL"),
AccountName: accountName,
Username: os.Getenv("TRITON_USER"),
Signers: []authentication.Signer{sshKeySigner},
}
c, err := compute.NewClient(config)
if err != nil {
log.Fatalf("compute.NewClient: %s", err)
}
c, err := compute.NewClient(config)
if err != nil {
log.Fatalf("compute.NewClient: %s", err)
}
```
Constructing `compute.Client` returns an interface which exposes `compute` API
@ -55,10 +64,10 @@ The same `triton.ClientConfig` will initialize the Manta `storage` client as
well...
```go
c, err := storage.NewClient(config)
if err != nil {
log.Fatalf("storage.NewClient: %s", err)
}
c, err := storage.NewClient(config)
if err != nil {
log.Fatalf("storage.NewClient: %s", err)
}
```
## Error Handling
@ -79,13 +88,14 @@ set:
- `TRITON_TEST` - must be set to any value in order to indicate desire to create
resources
- `SDC_URL` - the base endpoint for the Triton API
- `SDC_ACCOUNT` - the account name for the Triton API
- `SDC_KEY_ID` - the fingerprint of the SSH key identifying the key
- `TRITON_URL` - the base endpoint for the Triton API
- `TRITON_ACCOUNT` - the account name for the Triton API
- `TRITON_KEY_ID` - the fingerprint of the SSH key identifying the key
Additionally, you may set `SDC_KEY_MATERIAL` to the contents of an unencrypted
Additionally, you may set `TRITON_KEY_MATERIAL` to the contents of an unencrypted
private key. If this is set, the PrivateKeySigner (see above) will be used - if
not the SSHAgentSigner will be used.
not the SSHAgentSigner will be used. You can also set `TRITON_USER` to run the tests
against an account other than the main Triton account
### Example Run
@ -94,9 +104,9 @@ The verbose output has been removed for brevity here.
```
$ HTTP_PROXY=http://localhost:8888 \
TRITON_TEST=1 \
SDC_URL=https://us-sw-1.api.joyent.com \
SDC_ACCOUNT=AccountName \
SDC_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \
TRITON_URL=https://us-sw-1.api.joyent.com \
TRITON_ACCOUNT=AccountName \
TRITON_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \
go test -v -run "TestAccKey"
=== RUN TestAccKey_Create
--- PASS: TestAccKey_Create (12.46s)
@ -116,7 +126,7 @@ referencing your SSH key file use by your active `triton` CLI profile.
```sh
$ eval "$(triton env us-sw-1)"
$ SDC_KEY_FILE=~/.ssh/triton-id_rsa go run examples/compute/instances.go
$ TRITON_KEY_FILE=~/.ssh/triton-id_rsa go run examples/compute/instances.go
```
The following is a complete example of how to initialize the `compute` package
@ -142,15 +152,21 @@ import (
)
func main() {
keyID := os.Getenv("SDC_KEY_ID")
accountName := os.Getenv("SDC_ACCOUNT")
keyMaterial := os.Getenv("SDC_KEY_MATERIAL")
keyID := os.Getenv("TRITON_KEY_ID")
accountName := os.Getenv("TRITON_ACCOUNT")
keyMaterial := os.Getenv("TRITON_KEY_MATERIAL")
userName := os.Getenv("TRITON_USER")
var signer authentication.Signer
var err error
if keyMaterial == "" {
signer, err = authentication.NewSSHAgentSigner(keyID, accountName)
input := authentication.SSHAgentSignerInput{
KeyID: keyID,
AccountName: accountName,
Username: userName,
}
signer, err = authentication.NewSSHAgentSigner(input)
if err != nil {
log.Fatalf("Error Creating SSH Agent Signer: {{err}}", err)
}
@ -178,15 +194,22 @@ func main() {
keyBytes = []byte(keyMaterial)
}
signer, err = authentication.NewPrivateKeySigner(keyID, []byte(keyMaterial), accountName)
input := authentication.PrivateKeySignerInput{
KeyID: keyID,
PrivateKeyMaterial: keyBytes,
AccountName: accountName,
Username: userName,
}
signer, err = authentication.NewPrivateKeySigner(input)
if err != nil {
log.Fatalf("Error Creating SSH Private Key Signer: {{err}}", err)
}
}
config := &triton.ClientConfig{
TritonURL: os.Getenv("SDC_URL"),
TritonURL: os.Getenv("TRITON_URL"),
AccountName: accountName,
Username: userName,
Signers: []authentication.Signer{signer},
}

View File

@ -0,0 +1,72 @@
package authentication
// DON'T USE THIS OUTSIDE TESTING ~ This key was only created to use for
// internal unit testing. It should never be used for acceptance testing either.
//
// This is just a randomly generated key pair.
var Dummy = struct {
Fingerprint string
PrivateKey []byte
PublicKey []byte
Signer Signer
}{
"9f:d6:65:fc:d6:60:dc:d0:4e:db:2d:75:f7:92:8c:31",
[]byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`),
[]byte(`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6L2U2MIlqEd4VIULodeL8I1fnzwL8vaAlTHrluc2mFUL3yFbXDOhG89cfHlpadvuURuk2E/oHRSgIENNo+C32Ed8P+g1D7odPVCkMgZ9mZcj3X+1yMZVIFvE3tZpi38ew+saNtqXC9A8KE3newxWYWQxMQNHkbfM4E4Aas6KaUJPzTAjZLleDSyl3yOMozkjWzANHTG24NacdNHU6GKf6kRAgTqCUtN9iruhDHqeYxdcE2aT5Jo8PUUDKOQX/zWKbYQdcHKA0TCtpWsNwDEraYqVTZcwmKiBAo1haWbdSRs0HpY8t7Xb4prTauYhWxKoilz9kIGtoCUJpJaVgKRT68naEf/7KnLltc2VtpOJZkr6+4js8LztPnTIs15dW41kDKfMOkEK+yOKIesH9kVF9V8Xo++JXwJX6bsZPN8DAmFV5JuWtHqe2zXorQdMPjy/nsNTRvS3lU68LtxY9KGz5p91IvdnxF4vkRYyBTXB++3JxEp6GINUaPcyzZx/zdEgA2vfzsb7UtxNBg/skm4HtK/LmP5Hr4eMjRyC7gQxQ91Z8I0YbKuXHAk20+ZRJ9qwfXFQqmqvt0L7Sw3j+B8nDvlZMBisBOQ/N7Ghg1oywYN+G6iGRhrdOfarWDeSqGxf9HQV3l3x3mpzXfupxELGJljP1/dtWRdBuFiY711rcqw== test-dummy-20171002140848`),
nil,
}
func init() {
testSigner, _ := NewTestSigner()
Dummy.Signer = testSigner
}

View File

@ -9,6 +9,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"path"
"strings"
"github.com/hashicorp/errwrap"
@ -20,15 +21,23 @@ type PrivateKeySigner struct {
keyFingerprint string
algorithm string
accountName string
userName string
hashFunc crypto.Hash
privateKey *rsa.PrivateKey
}
func NewPrivateKeySigner(keyFingerprint string, privateKeyMaterial []byte, accountName string) (*PrivateKeySigner, error) {
keyFingerprintMD5 := strings.Replace(keyFingerprint, ":", "", -1)
type PrivateKeySignerInput struct {
KeyID string
PrivateKeyMaterial []byte
AccountName string
Username string
}
block, _ := pem.Decode(privateKeyMaterial)
func NewPrivateKeySigner(input PrivateKeySignerInput) (*PrivateKeySigner, error) {
keyFingerprintMD5 := strings.Replace(input.KeyID, ":", "", -1)
block, _ := pem.Decode(input.PrivateKeyMaterial)
if block == nil {
return nil, errors.New("Error PEM-decoding private key material: nil block received")
}
@ -51,13 +60,17 @@ func NewPrivateKeySigner(keyFingerprint string, privateKeyMaterial []byte, accou
signer := &PrivateKeySigner{
formattedKeyFingerprint: displayKeyFingerprint,
keyFingerprint: keyFingerprint,
accountName: accountName,
keyFingerprint: input.KeyID,
accountName: input.AccountName,
hashFunc: crypto.SHA1,
privateKey: rsakey,
}
if input.Username != "" {
signer.userName = input.Username
}
_, algorithm, err := signer.SignRaw("HelloWorld")
if err != nil {
return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err)
@ -80,7 +93,13 @@ func (s *PrivateKeySigner) Sign(dateHeader string) (string, error) {
}
signedBase64 := base64.StdEncoding.EncodeToString(signed)
keyID := fmt.Sprintf("/%s/keys/%s", s.accountName, s.formattedKeyFingerprint)
var keyID string
if s.userName != "" {
keyID = path.Join("/", s.accountName, "users", s.userName, "keys", s.formattedKeyFingerprint)
} else {
keyID = path.Join("/", s.accountName, "keys", s.formattedKeyFingerprint)
}
return fmt.Sprintf(authorizationHeaderFormat, keyID, "rsa-sha1", headerName, signedBase64), nil
}

View File

@ -8,6 +8,7 @@ import (
"fmt"
"net"
"os"
"path"
"strings"
"github.com/hashicorp/errwrap"
@ -15,21 +16,32 @@ import (
"golang.org/x/crypto/ssh/agent"
)
var (
ErrUnsetEnvVar = errors.New("SSH_AUTH_SOCK is not set")
)
type SSHAgentSigner struct {
formattedKeyFingerprint string
keyFingerprint string
algorithm string
accountName string
userName string
keyIdentifier string
agent agent.Agent
key ssh.PublicKey
}
func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, error) {
sshAgentAddress := os.Getenv("SSH_AUTH_SOCK")
if sshAgentAddress == "" {
return nil, errors.New("SSH_AUTH_SOCK is not set")
type SSHAgentSignerInput struct {
KeyID string
AccountName string
Username string
}
func NewSSHAgentSigner(input SSHAgentSignerInput) (*SSHAgentSigner, error) {
sshAgentAddress, agentOk := os.LookupEnv("SSH_AUTH_SOCK")
if !agentOk {
return nil, ErrUnsetEnvVar
}
conn, err := net.Dial("unix", sshAgentAddress)
@ -39,12 +51,41 @@ func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, err
ag := agent.NewClient(conn)
keys, err := ag.List()
signer := &SSHAgentSigner{
keyFingerprint: input.KeyID,
accountName: input.AccountName,
agent: ag,
}
matchingKey, err := signer.MatchKey()
if err != nil {
return nil, err
}
signer.key = matchingKey
signer.formattedKeyFingerprint = formatPublicKeyFingerprint(signer.key, true)
if input.Username != "" {
signer.userName = input.Username
signer.keyIdentifier = path.Join("/", signer.accountName, "users", input.Username, "keys", signer.formattedKeyFingerprint)
} else {
signer.keyIdentifier = path.Join("/", signer.accountName, "keys", signer.formattedKeyFingerprint)
}
_, algorithm, err := signer.SignRaw("HelloWorld")
if err != nil {
return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err)
}
signer.algorithm = algorithm
return signer, nil
}
func (s *SSHAgentSigner) MatchKey() (ssh.PublicKey, error) {
keys, err := s.agent.List()
if err != nil {
return nil, errwrap.Wrapf("Error listing keys in SSH Agent: %s", err)
}
keyFingerprintStripped := strings.TrimPrefix(keyFingerprint, "MD5:")
keyFingerprintStripped := strings.TrimPrefix(s.keyFingerprint, "MD5:")
keyFingerprintStripped = strings.TrimPrefix(keyFingerprintStripped, "SHA256:")
keyFingerprintStripped = strings.Replace(keyFingerprintStripped, ":", "", -1)
@ -64,27 +105,10 @@ func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, err
}
if matchingKey == nil {
return nil, fmt.Errorf("No key in the SSH Agent matches fingerprint: %s", keyFingerprint)
return nil, fmt.Errorf("No key in the SSH Agent matches fingerprint: %s", s.keyFingerprint)
}
formattedKeyFingerprint := formatPublicKeyFingerprint(matchingKey, true)
signer := &SSHAgentSigner{
formattedKeyFingerprint: formattedKeyFingerprint,
keyFingerprint: keyFingerprint,
accountName: accountName,
agent: ag,
key: matchingKey,
keyIdentifier: fmt.Sprintf("/%s/keys/%s", accountName, formattedKeyFingerprint),
}
_, algorithm, err := signer.SignRaw("HelloWorld")
if err != nil {
return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err)
}
signer.algorithm = algorithm
return signer, nil
return matchingKey, nil
}
func (s *SSHAgentSigner) Sign(dateHeader string) (string, error) {

View File

@ -0,0 +1,27 @@
package authentication
// TestSigner represents an authentication key signer which we can use for
// testing purposes only. This will largely be a stub to send through client
// unit tests.
type TestSigner struct{}
// NewTestSigner constructs a new instance of test signer
func NewTestSigner() (Signer, error) {
return &TestSigner{}, nil
}
func (s *TestSigner) DefaultAlgorithm() string {
return ""
}
func (s *TestSigner) KeyFingerprint() string {
return ""
}
func (s *TestSigner) Sign(dateHeader string) (string, error) {
return "", nil
}
func (s *TestSigner) SignRaw(toSign string) (string, string, error) {
return "", "", nil
}

View File

@ -6,6 +6,7 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
@ -19,7 +20,14 @@ import (
const nilContext = "nil context"
var MissingKeyIdError = errors.New("Default SSH agent authentication requires SDC_KEY_ID")
var (
ErrDefaultAuth = errors.New("default SSH agent authentication requires SDC_KEY_ID / TRITON_KEY_ID and SSH_AUTH_SOCK")
ErrAccountName = errors.New("missing account name for Triton/Manta")
ErrMissingURL = errors.New("missing Triton and/or Manta URL")
BadTritonURL = "invalid format of triton URL"
BadMantaURL = "invalid format of manta URL"
)
// Client represents a connection to the Triton Compute or Object Storage APIs.
type Client struct {
@ -28,7 +36,7 @@ type Client struct {
TritonURL url.URL
MantaURL url.URL
AccountName string
Endpoint string
Username string
}
// New is used to construct a Client in order to make API
@ -37,61 +45,93 @@ type Client struct {
// At least one signer must be provided - example signers include
// authentication.PrivateKeySigner and authentication.SSHAgentSigner.
func New(tritonURL string, mantaURL string, accountName string, signers ...authentication.Signer) (*Client, error) {
if accountName == "" {
return nil, ErrAccountName
}
if tritonURL == "" && mantaURL == "" {
return nil, ErrMissingURL
}
cloudURL, err := url.Parse(tritonURL)
if err != nil {
return nil, errwrap.Wrapf("invalid endpoint URL: {{err}}", err)
return nil, errwrap.Wrapf(BadTritonURL+": {{err}}", err)
}
storageURL, err := url.Parse(mantaURL)
if err != nil {
return nil, errwrap.Wrapf("invalid manta URL: {{err}}", err)
return nil, errwrap.Wrapf(BadMantaURL+": {{err}}", err)
}
if accountName == "" {
return nil, errors.New("account name can not be empty")
}
httpClient := &http.Client{
Transport: httpTransport(false),
CheckRedirect: doNotFollowRedirects,
}
newClient := &Client{
HTTPClient: httpClient,
Authorizers: signers,
TritonURL: *cloudURL,
MantaURL: *storageURL,
AccountName: accountName,
// TODO(justinwr): Deprecated?
// Endpoint: tritonURL,
}
var authorizers []authentication.Signer
authorizers := make([]authentication.Signer, 0)
for _, key := range signers {
if key != nil {
authorizers = append(authorizers, key)
}
}
newClient := &Client{
HTTPClient: &http.Client{
Transport: httpTransport(false),
CheckRedirect: doNotFollowRedirects,
},
Authorizers: authorizers,
TritonURL: *cloudURL,
MantaURL: *storageURL,
AccountName: accountName,
}
// Default to constructing an SSHAgentSigner if there are no other signers
// passed into NewClient and there's an SDC_KEY_ID value available in the
// user environ.
if len(authorizers) == 0 {
keyID := os.Getenv("SDC_KEY_ID")
if len(keyID) != 0 {
keySigner, err := authentication.NewSSHAgentSigner(keyID, accountName)
if err != nil {
return nil, errwrap.Wrapf("Problem initializing NewSSHAgentSigner: {{err}}", err)
}
newClient.Authorizers = append(authorizers, keySigner)
} else {
return nil, MissingKeyIdError
// passed into NewClient and there's an TRITON_KEY_ID and SSH_AUTH_SOCK
// available in the user's environ(7).
if len(newClient.Authorizers) == 0 {
if err := newClient.DefaultAuth(); err != nil {
return nil, err
}
}
return newClient, nil
}
var envPrefixes = []string{"TRITON", "SDC"}
// GetTritonEnv looks up environment variables using the preferred "TRITON"
// prefix, but falls back to the SDC prefix. For example, looking up "USER"
// will search for "TRITON_USER" followed by "SDC_USER". If the environment
// variable is not set, an empty string is returned. GetTritonEnv() is used to
// aid in the transition and deprecation of the SDC_* environment variables.
func GetTritonEnv(name string) string {
for _, prefix := range envPrefixes {
if val, found := os.LookupEnv(prefix + "_" + name); found {
return val
}
}
return ""
}
// initDefaultAuth provides a default key signer for a client. This should only
// be used internally if the client has no other key signer for authenticating
// with Triton. We first look for both `SDC_KEY_ID` and `SSH_AUTH_SOCK` in the
// user's environ(7). If so we default to the SSH agent key signer.
func (c *Client) DefaultAuth() error {
tritonKeyId := GetTritonEnv("KEY_ID")
if tritonKeyId != "" {
input := authentication.SSHAgentSignerInput{
KeyID: tritonKeyId,
AccountName: c.AccountName,
Username: c.Username,
}
defaultSigner, err := authentication.NewSSHAgentSigner(input)
if err != nil {
return errwrap.Wrapf("problem initializing NewSSHAgentSigner: {{err}}", err)
}
c.Authorizers = append(c.Authorizers, defaultSigner)
}
return ErrDefaultAuth
}
// InsecureSkipTLSVerify turns off TLS verification for the client connection. This
// allows connection to an endpoint with a certificate which was signed by a non-
// trusted CA, such as self-signed certificates. This can be useful when connecting
@ -112,8 +152,8 @@ func httpTransport(insecureSkipTLSVerify bool) *http.Transport {
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
DisableKeepAlives: true,
MaxIdleConnsPerHost: -1,
MaxIdleConns: 10,
IdleConnTimeout: 15 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecureSkipTLSVerify,
},
@ -158,7 +198,7 @@ func (c *Client) ExecuteRequestURIParams(ctx context.Context, inputs RequestInpu
body := inputs.Body
query := inputs.Query
var requestBody io.ReadSeeker
var requestBody io.Reader
if body != nil {
marshaled, err := json.MarshalIndent(body, "", " ")
if err != nil {
@ -217,7 +257,7 @@ func (c *Client) ExecuteRequestRaw(ctx context.Context, inputs RequestInput) (*h
path := inputs.Path
body := inputs.Body
var requestBody io.ReadSeeker
var requestBody io.Reader
if body != nil {
marshaled, err := json.MarshalIndent(body, "", " ")
if err != nil {
@ -270,7 +310,7 @@ func (c *Client) ExecuteRequestStorage(ctx context.Context, inputs RequestInput)
endpoint := c.MantaURL
endpoint.Path = path
var requestBody io.ReadSeeker
var requestBody io.Reader
if body != nil {
marshaled, err := json.MarshalIndent(body, "", " ")
if err != nil {
@ -323,10 +363,17 @@ func (c *Client) ExecuteRequestStorage(ctx context.Context, inputs RequestInput)
StatusCode: resp.StatusCode,
}
errorDecoder := json.NewDecoder(resp.Body)
if err := errorDecoder.Decode(mantaError); err != nil {
return nil, nil, errwrap.Wrapf("Error decoding error response: {{err}}", err)
if req.Method != http.MethodHead {
errorDecoder := json.NewDecoder(resp.Body)
if err := errorDecoder.Decode(mantaError); err != nil {
return nil, nil, errwrap.Wrapf("Error decoding error response: {{err}}", err)
}
}
if mantaError.Message == "" {
mantaError.Message = fmt.Sprintf("HTTP response returned status code %d", resp.StatusCode)
}
return nil, nil, mantaError
}
@ -335,7 +382,7 @@ type RequestNoEncodeInput struct {
Path string
Query *url.Values
Headers *http.Header
Body io.ReadSeeker
Body io.Reader
}
func (c *Client) ExecuteRequestNoEncode(ctx context.Context, inputs RequestNoEncodeInput) (io.ReadCloser, http.Header, error) {

View File

@ -38,7 +38,7 @@ func (c *ComputeClient) Images() *ImagesClient {
return &ImagesClient{c.Client}
}
// Machines returns a Compute client used for accessing functions pertaining to
// Machine returns a Compute client used for accessing functions pertaining to
// machine functionality in the Triton API.
func (c *ComputeClient) Instances() *InstancesClient {
return &InstancesClient{c.Client}
@ -55,3 +55,9 @@ func (c *ComputeClient) Packages() *PackagesClient {
func (c *ComputeClient) Services() *ServicesClient {
return &ServicesClient{c.Client}
}
// Snapshots returns a Compute client used for accessing functions pertaining to
// Snapshots functionality in the Triton API.
func (c *ComputeClient) Snapshots() *SnapshotsClient {
return &SnapshotsClient{c.Client}
}

View File

@ -81,7 +81,7 @@ func (c *DataCentersClient) Get(ctx context.Context, input *GetDataCenterInput)
}
if resp.StatusCode != http.StatusFound {
return nil, fmt.Errorf("Error executing Get request: expected status code 302, got %s",
return nil, fmt.Errorf("Error executing Get request: expected status code 302, got %d",
resp.StatusCode)
}

View File

@ -94,6 +94,12 @@ func IsUnknownError(err error) bool {
return isSpecificError(err, "UnknownError")
}
// IsEmptyResponse tests whether err wraps a client.TritonError with code
// EmptyResponse
func IsEmptyResponse(err error) bool {
return isSpecificError(err, "EmptyResponse")
}
// isSpecificError checks whether the error represented by err wraps
// an underlying client.TritonError with code errorCode.
func isSpecificError(err error, errorCode string) bool {

View File

@ -97,10 +97,13 @@ func (c *InstancesClient) Get(ctx context.Context, input *GetInstanceInput) (*In
Path: path,
}
response, err := c.client.ExecuteRequestRaw(ctx, reqInputs)
if response != nil {
if response == nil {
return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err)
}
if response.Body != nil {
defer response.Body.Close()
}
if response == nil || response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone {
if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone {
return nil, &client.TritonError{
StatusCode: response.StatusCode,
Code: "ResourceNotFound",
@ -962,6 +965,32 @@ func (c *InstancesClient) Start(ctx context.Context, input *StartInstanceInput)
return nil
}
type RebootInstanceInput struct {
InstanceID string
}
func (c *InstancesClient) Reboot(ctx context.Context, input *RebootInstanceInput) error {
path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.InstanceID)
params := &url.Values{}
params.Set("action", "reboot")
reqInputs := client.RequestInput{
Method: http.MethodPost,
Path: path,
Query: params,
}
respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing Start request: {{err}}", err)
}
return nil
}
var reservedInstanceCNSTags = map[string]struct{}{
CNSTagDisable: {},
CNSTagReversePTR: {},

56
vendor/github.com/joyent/triton-go/compute/ping.go generated vendored Normal file
View File

@ -0,0 +1,56 @@
package compute
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/hashicorp/errwrap"
"github.com/joyent/triton-go/client"
)
const pingEndpoint = "/--ping"
type CloudAPI struct {
Versions []string `json:"versions"`
}
type PingOutput struct {
Ping string `json:"ping"`
CloudAPI CloudAPI `json:"cloudapi"`
}
// Ping sends a request to the '/--ping' endpoint and returns a `pong` as well
// as a list of API version numbers your instance of CloudAPI is presenting.
func (c *ComputeClient) Ping(ctx context.Context) (*PingOutput, error) {
reqInputs := client.RequestInput{
Method: http.MethodGet,
Path: pingEndpoint,
}
response, err := c.Client.ExecuteRequestRaw(ctx, reqInputs)
if response == nil {
return nil, fmt.Errorf("Ping request has empty response")
}
if response.Body != nil {
defer response.Body.Close()
}
if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone {
return nil, &client.TritonError{
StatusCode: response.StatusCode,
Code: "ResourceNotFound",
}
}
if err != nil {
return nil, errwrap.Wrapf("Error executing Get request: {{err}}",
c.Client.DecodeError(response.StatusCode, response.Body))
}
var result *PingOutput
decoder := json.NewDecoder(response.Body)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding Get response: {{err}}", err)
}
return result, nil
}

157
vendor/github.com/joyent/triton-go/compute/snapshots.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
package compute
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/hashicorp/errwrap"
"github.com/joyent/triton-go/client"
)
type SnapshotsClient struct {
client *client.Client
}
type Snapshot struct {
Name string
State string
Created time.Time
Updated time.Time
}
type ListSnapshotsInput struct {
MachineID string
}
func (c *SnapshotsClient) List(ctx context.Context, input *ListSnapshotsInput) ([]*Snapshot, error) {
path := fmt.Sprintf("/%s/machines/%s/snapshots", c.client.AccountName, input.MachineID)
reqInputs := client.RequestInput{
Method: http.MethodGet,
Path: path,
}
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing List request: {{err}}", err)
}
var result []*Snapshot
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding List response: {{err}}", err)
}
return result, nil
}
type GetSnapshotInput struct {
MachineID string
Name string
}
func (c *SnapshotsClient) Get(ctx context.Context, input *GetSnapshotInput) (*Snapshot, error) {
path := fmt.Sprintf("/%s/machines/%s/snapshots/%s", c.client.AccountName, input.MachineID, input.Name)
reqInputs := client.RequestInput{
Method: http.MethodGet,
Path: path,
}
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err)
}
var result *Snapshot
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding Get response: {{err}}", err)
}
return result, nil
}
type DeleteSnapshotInput struct {
MachineID string
Name string
}
func (c *SnapshotsClient) Delete(ctx context.Context, input *DeleteSnapshotInput) error {
path := fmt.Sprintf("/%s/machines/%s/snapshots/%s", c.client.AccountName, input.MachineID, input.Name)
reqInputs := client.RequestInput{
Method: http.MethodDelete,
Path: path,
}
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing Delete request: {{err}}", err)
}
return nil
}
type StartMachineFromSnapshotInput struct {
MachineID string
Name string
}
func (c *SnapshotsClient) StartMachine(ctx context.Context, input *StartMachineFromSnapshotInput) error {
path := fmt.Sprintf("/%s/machines/%s/snapshots/%s", c.client.AccountName, input.MachineID, input.Name)
reqInputs := client.RequestInput{
Method: http.MethodPost,
Path: path,
}
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing StartMachine request: {{err}}", err)
}
return nil
}
type CreateSnapshotInput struct {
MachineID string
Name string
}
func (c *SnapshotsClient) Create(ctx context.Context, input *CreateSnapshotInput) (*Snapshot, error) {
path := fmt.Sprintf("/%s/machines/%s/snapshots", c.client.AccountName, input.MachineID)
data := make(map[string]interface{})
data["name"] = input.Name
reqInputs := client.RequestInput{
Method: http.MethodPost,
Path: path,
Body: data,
}
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing Create request: {{err}}", err)
}
var result *Snapshot
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding Create response: {{err}}", err)
}
return result, nil
}

View File

@ -6,6 +6,8 @@ import (
"fmt"
"net/http"
"time"
"github.com/hashicorp/errwrap"
"github.com/joyent/triton-go/client"
)
@ -227,7 +229,7 @@ type ListMachineRulesInput struct {
}
func (c *FirewallClient) ListMachineRules(ctx context.Context, input *ListMachineRulesInput) ([]*FirewallRule, error) {
path := fmt.Sprintf("/%s/machines/%s/firewallrules", c.client.AccountName, input.MachineID)
path := fmt.Sprintf("/%s/machines/%s/fwrules", c.client.AccountName, input.MachineID)
reqInputs := client.RequestInput{
Method: http.MethodGet,
Path: path,
@ -243,7 +245,56 @@ func (c *FirewallClient) ListMachineRules(ctx context.Context, input *ListMachin
var result []*FirewallRule
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding ListRules response: {{err}}", err)
return nil, errwrap.Wrapf("Error decoding ListMachineRules response: {{err}}", err)
}
return result, nil
}
type ListRuleMachinesInput struct {
ID string
}
type Machine struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Brand string `json:"brand"`
State string `json:"state"`
Image string `json:"image"`
Memory int `json:"memory"`
Disk int `json:"disk"`
Metadata map[string]string `json:"metadata"`
Tags map[string]interface{} `json:"tags"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Docker bool `json:"docker"`
IPs []string `json:"ips"`
Networks []string `json:"networks"`
PrimaryIP string `json:"primaryIp"`
FirewallEnabled bool `json:"firewall_enabled"`
ComputeNode string `json:"compute_node"`
Package string `json:"package"`
}
func (c *FirewallClient) ListRuleMachines(ctx context.Context, input *ListRuleMachinesInput) ([]*Machine, error) {
path := fmt.Sprintf("/%s/fwrules/%s/machines", c.client.AccountName, input.ID)
reqInputs := client.RequestInput{
Method: http.MethodGet,
Path: path,
}
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing ListRuleMachines request: {{err}}", err)
}
var result []*Machine
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding ListRuleMachines response: {{err}}", err)
}
return result, nil

View File

@ -14,5 +14,6 @@ type ClientConfig struct {
TritonURL string
MantaURL string
AccountName string
Username string
Signers []authentication.Signer
}

30
vendor/vendor.json vendored
View File

@ -823,34 +823,34 @@
"revision": "c01cf91b011868172fdcd9f41838e80c9d716264"
},
{
"checksumSHA1": "EqvUu0Ku0Ec5Tk6yhGNOuOr8yeA=",
"checksumSHA1": "oINoQSRkPinChzwEHr3VatB9++Y=",
"path": "github.com/joyent/triton-go",
"revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0",
"revisionTime": "2017-10-17T16:55:58Z"
"revision": "86ba9699869b6cd5ea3290faad7be659efc7d6ce",
"revisionTime": "2017-12-28T20:20:46Z"
},
{
"checksumSHA1": "JKf97EAAAZFQ6Wf8qN9X7TWqNBY=",
"checksumSHA1": "d6pxw8DLxYehLr92fWZTLEWVws8=",
"path": "github.com/joyent/triton-go/authentication",
"revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0",
"revisionTime": "2017-10-17T16:55:58Z"
"revision": "86ba9699869b6cd5ea3290faad7be659efc7d6ce",
"revisionTime": "2017-12-28T20:20:46Z"
},
{
"checksumSHA1": "dlO1or0cyVMAmZzyLcBuoy+M0xU=",
"checksumSHA1": "GCHfn8d1Mhswm7n7IRnT0n/w+dw=",
"path": "github.com/joyent/triton-go/client",
"revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0",
"revisionTime": "2017-10-17T16:55:58Z"
"revision": "86ba9699869b6cd5ea3290faad7be659efc7d6ce",
"revisionTime": "2017-12-28T20:20:46Z"
},
{
"checksumSHA1": "O/y7BfKJFUf3A8TCRMXgo9HSb1w=",
"checksumSHA1": "U9D/fCNr+1uD1p/O0PW0yD/izqc=",
"path": "github.com/joyent/triton-go/compute",
"revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0",
"revisionTime": "2017-10-17T16:55:58Z"
"revision": "86ba9699869b6cd5ea3290faad7be659efc7d6ce",
"revisionTime": "2017-12-28T20:20:46Z"
},
{
"checksumSHA1": "gyLtPyKlcumRSkrAH+SsDQo1GnY=",
"checksumSHA1": "9OdR/eI3qbmADruKn6yE1Dbx3LE=",
"path": "github.com/joyent/triton-go/network",
"revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0",
"revisionTime": "2017-10-17T16:55:58Z"
"revision": "86ba9699869b6cd5ea3290faad7be659efc7d6ce",
"revisionTime": "2017-12-28T20:20:46Z"
},
{
"checksumSHA1": "gEjGS03N1eysvpQ+FCHTxPcbxXc=",

View File

@ -92,6 +92,9 @@ builder.
of `triton_key_id` is stored. For example `/home/soandso/.ssh/id_rsa`. If
this is not specified, the SSH agent is used to sign requests with the
`triton_key_id` specified.
- `triton_user` (string) - The username of a user who has access to your Triton
account.
- `source_machine_firewall_enabled` (boolean) - Whether or not the firewall of
the VM used to create an image of is enabled. The Triton firewall only
@ -149,7 +152,7 @@ builder.
## Basic Example
Below is a minimal example to create an joyent-brand image on the Joyent public
Below is a minimal example to create an image on the Joyent public
cloud:
``` json