1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

switch to new go-acme imports from xenolf. Fix api changes (#540)

* switch to new go-acme imports from xenolf. Fix api changes

* update many vault related dependencies
This commit is contained in:
Craig Peterson
2019-07-29 10:54:32 -04:00
committed by GitHub
parent cafd4d387a
commit 2ee086d41c
177 changed files with 10978 additions and 4075 deletions

View File

@ -39,6 +39,7 @@ type GetCertsArgs struct {
Verbose bool
Vault bool
VaultPath string
Only string
Notify bool
@ -111,6 +112,11 @@ func (args *GetCertsArgs) flags() []cli.Flag {
Destination: &args.Notify,
Usage: `set to true to send notifications to configured destinations`,
})
flags = append(flags, cli.StringFlag{
Name: "only",
Destination: &args.Only,
Usage: `Only check a single cert. Provide cert name.`,
})
return flags
}
@ -179,6 +185,9 @@ func GetCerts(args GetCertsArgs) error {
return err
}
for _, cert := range certList {
if args.Only != "" && cert.CertName != args.Only {
continue
}
v := args.Verbose || printer.DefaultPrinter.Verbose
issued, err := client.IssueOrRenewCert(cert, args.RenewUnderDays, v)
if issued || err != nil {

View File

@ -15,8 +15,12 @@ import (
"github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/pkg/nameservers"
"github.com/StackExchange/dnscontrol/pkg/notifications"
"github.com/xenolf/lego/acme"
acmelog "github.com/xenolf/lego/log"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/lego"
acmelog "github.com/go-acme/lego/log"
)
type CertConfig struct {
@ -101,10 +105,14 @@ func (c *certManager) IssueOrRenewCert(cfg *CertConfig, renewUnder int, verbose
return false, err
}
var client *acme.Client
var client *lego.Client
var action = func() (*acme.CertificateResource, error) {
return client.ObtainCertificate(cfg.Names, true, nil, cfg.MustStaple)
var action = func() (*certificate.Resource, error) {
return client.Certificate.Obtain(certificate.ObtainRequest{
Bundle: true,
Domains: cfg.Names,
MustStaple: cfg.MustStaple,
})
}
if existing == nil {
@ -125,25 +133,28 @@ func (c *certManager) IssueOrRenewCert(cfg *CertConfig, renewUnder int, verbose
log.Println("DNS Names don't match expected set. Reissuing.")
} else {
log.Println("Renewing cert")
action = func() (*acme.CertificateResource, error) {
return client.RenewCertificate(*existing, true, cfg.MustStaple)
action = func() (*certificate.Resource, error) {
return client.Certificate.Renew(*existing, true, cfg.MustStaple)
}
}
}
kt := acme.RSA2048
kt := certcrypto.RSA2048
if cfg.UseECC {
kt = acme.EC256
kt = certcrypto.EC256
}
client, err = acme.NewClient(c.acmeDirectory, c.account, kt)
config := lego.NewConfig(c.account)
config.CADirURL = c.acmeDirectory
config.Certificate.KeyType = kt
client, err = lego.NewClient(config)
if err != nil {
return false, err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSALPN01})
client.SetChallengeProvider(acme.DNS01, c)
client.Challenge.Remove(challenge.HTTP01)
client.Challenge.Remove(challenge.TLSALPN01)
client.Challenge.SetDNS01Provider(c)
acme.PreCheckDNS = c.preCheckDNS
defer func() { acme.PreCheckDNS = acmePreCheck }()
dns01.WrapPreCheck(c.preCheckDNS)
certResource, err := action()
if err != nil {
@ -219,7 +230,7 @@ func (c *certManager) Present(domain, token, keyAuth string) (e error) {
d = copy
}
fqdn, val, _ := acme.DNS01Record(domain, keyAuth)
fqdn, val := dns01.GetRecord(domain, keyAuth)
txt := &models.RecordConfig{Type: "TXT"}
txt.SetTargetTXT(val)
txt.SetLabelFromFQDN(fqdn, d.Name)

View File

@ -4,17 +4,15 @@ import (
"log"
"time"
"github.com/xenolf/lego/acme"
"github.com/go-acme/lego/challenge/dns01"
)
var acmePreCheck = acme.PreCheckDNS
func (c *certManager) preCheckDNS(fqdn, value string) (bool, error) {
func (c *certManager) preCheckDNS(domain, fqdn, value string, native dns01.PreCheckFunc) (bool, error) {
// default record verification in the client library makes sure the authoritative nameservers
// have the expected records.
// Sometimes the Let's Encrypt verification fails anyway because records have not propagated the provider's network fully.
// So we add an additional 60 second sleep just for safety.
v, err := acmePreCheck(fqdn, value)
v, err := native(fqdn, value)
if err != nil {
return v, err
}

View File

@ -9,7 +9,7 @@ import (
"os"
"path/filepath"
"github.com/xenolf/lego/acme"
"github.com/go-acme/lego/certificate"
)
// directoryStorage implements storage in a local file directory
@ -38,7 +38,7 @@ func (d directoryStorage) accountKeyFile(acmeHost string) string {
const perms os.FileMode = 0644
const dirPerms os.FileMode = 0700
func (d directoryStorage) GetCertificate(name string) (*acme.CertificateResource, error) {
func (d directoryStorage) GetCertificate(name string) (*certificate.Resource, error) {
f, err := os.Open(d.certFile(name, "json"))
if err != nil && os.IsNotExist(err) {
// if json does not exist, nothing does
@ -49,7 +49,7 @@ func (d directoryStorage) GetCertificate(name string) (*acme.CertificateResource
}
defer f.Close()
dec := json.NewDecoder(f)
cr := &acme.CertificateResource{}
cr := &certificate.Resource{}
if err = dec.Decode(cr); err != nil {
return nil, err
}
@ -62,7 +62,7 @@ func (d directoryStorage) GetCertificate(name string) (*acme.CertificateResource
return cr, nil
}
func (d directoryStorage) StoreCertificate(name string, cert *acme.CertificateResource) error {
func (d directoryStorage) StoreCertificate(name string, cert *certificate.Resource) error {
// make sure actual cert data never gets into metadata json
if err := os.MkdirAll(d.certDir(name), dirPerms); err != nil {
return err

View File

@ -6,7 +6,9 @@ import (
"crypto/elliptic"
"crypto/rand"
"github.com/xenolf/lego/acme"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/lego"
"github.com/go-acme/lego/registration"
)
func (c *certManager) getOrCreateAccount() (*Account, error) {
@ -35,11 +37,14 @@ func (c *certManager) createAccount(email string) (*Account, error) {
key: privateKey,
Email: c.email,
}
client, err := acme.NewClient(c.acmeDirectory, acct, acme.EC384)
config := lego.NewConfig(acct)
config.CADirURL = c.acmeDirectory
config.Certificate.KeyType = certcrypto.EC384
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
reg, err := client.Register(true)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
@ -48,9 +53,9 @@ func (c *certManager) createAccount(email string) (*Account, error) {
}
type Account struct {
Email string `json:"email"`
key *ecdsa.PrivateKey `json:"-"`
Registration *acme.RegistrationResource `json:"registration"`
Email string `json:"email"`
key *ecdsa.PrivateKey `json:"-"`
Registration *registration.Resource `json:"registration"`
}
func (a *Account) GetEmail() string {
@ -59,6 +64,6 @@ func (a *Account) GetEmail() string {
func (a *Account) GetPrivateKey() crypto.PrivateKey {
return a.key
}
func (a *Account) GetRegistration() *acme.RegistrationResource {
func (a *Account) GetRegistration() *registration.Resource {
return a.Registration
}

View File

@ -1,12 +1,12 @@
package acme
import "github.com/xenolf/lego/acme"
import "github.com/go-acme/lego/certificate"
// Storage is an abstracrion around how certificates, keys, and account info are stored on disk or elsewhere.
type Storage interface {
// Get Existing certificate, or return nil if it does not exist
GetCertificate(name string) (*acme.CertificateResource, error)
StoreCertificate(name string, cert *acme.CertificateResource) error
GetCertificate(name string) (*certificate.Resource, error)
StoreCertificate(name string, cert *certificate.Resource) error
GetAccount(acmeHost string) (*Account, error)
StoreAccount(acmeHost string, account *Account) error

View File

@ -7,7 +7,7 @@ import (
"fmt"
"strings"
"github.com/xenolf/lego/acme"
"github.com/go-acme/lego/certificate"
"github.com/hashicorp/vault/api"
)
@ -32,7 +32,7 @@ func makeVaultStorage(vaultPath string) (Storage, error) {
return storage, nil
}
func (v *vaultStorage) GetCertificate(name string) (*acme.CertificateResource, error) {
func (v *vaultStorage) GetCertificate(name string) (*certificate.Resource, error) {
path := v.certPath(name)
secret, err := v.client.Read(path)
if err != nil {
@ -41,7 +41,7 @@ func (v *vaultStorage) GetCertificate(name string) (*acme.CertificateResource, e
if secret == nil {
return nil, nil
}
cert := &acme.CertificateResource{}
cert := &certificate.Resource{}
if dat, err := v.getString("meta", secret.Data, path); err != nil {
return nil, err
} else if err = json.Unmarshal(dat, cert); err != nil {
@ -75,7 +75,7 @@ func (v *vaultStorage) getString(key string, data map[string]interface{}, path s
return []byte(str), nil
}
func (v *vaultStorage) StoreCertificate(name string, cert *acme.CertificateResource) error {
func (v *vaultStorage) StoreCertificate(name string, cert *certificate.Resource) error {
jDat, err := json.MarshalIndent(cert, "", " ")
if err != nil {
return err

20
vendor/github.com/cenkalti/backoff/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Cenk Altı
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.

66
vendor/github.com/cenkalti/backoff/backoff.go generated vendored Normal file
View File

@ -0,0 +1,66 @@
// Package backoff implements backoff algorithms for retrying operations.
//
// Use Retry function for retrying operations that may fail.
// If Retry does not meet your needs,
// copy/paste the function into your project and modify as you wish.
//
// There is also Ticker type similar to time.Ticker.
// You can use it if you need to work with channels.
//
// See Examples section below for usage examples.
package backoff
import "time"
// BackOff is a backoff policy for retrying an operation.
type BackOff interface {
// NextBackOff returns the duration to wait before retrying the operation,
// or backoff. Stop to indicate that no more retries should be made.
//
// Example usage:
//
// duration := backoff.NextBackOff();
// if (duration == backoff.Stop) {
// // Do not retry operation.
// } else {
// // Sleep for duration and retry operation.
// }
//
NextBackOff() time.Duration
// Reset to initial state.
Reset()
}
// Stop indicates that no more retries should be made for use in NextBackOff().
const Stop time.Duration = -1
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
// meaning that the operation is retried immediately without waiting, indefinitely.
type ZeroBackOff struct{}
func (b *ZeroBackOff) Reset() {}
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
// NextBackOff(), meaning that the operation should never be retried.
type StopBackOff struct{}
func (b *StopBackOff) Reset() {}
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
// This is in contrast to an exponential backoff policy,
// which returns a delay that grows longer as you call NextBackOff() over and over again.
type ConstantBackOff struct {
Interval time.Duration
}
func (b *ConstantBackOff) Reset() {}
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
return &ConstantBackOff{Interval: d}
}

63
vendor/github.com/cenkalti/backoff/context.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
package backoff
import (
"context"
"time"
)
// BackOffContext is a backoff policy that stops retrying after the context
// is canceled.
type BackOffContext interface {
BackOff
Context() context.Context
}
type backOffContext struct {
BackOff
ctx context.Context
}
// WithContext returns a BackOffContext with context ctx
//
// ctx must not be nil
func WithContext(b BackOff, ctx context.Context) BackOffContext {
if ctx == nil {
panic("nil context")
}
if b, ok := b.(*backOffContext); ok {
return &backOffContext{
BackOff: b.BackOff,
ctx: ctx,
}
}
return &backOffContext{
BackOff: b,
ctx: ctx,
}
}
func ensureContext(b BackOff) BackOffContext {
if cb, ok := b.(BackOffContext); ok {
return cb
}
return WithContext(b, context.Background())
}
func (b *backOffContext) Context() context.Context {
return b.ctx
}
func (b *backOffContext) NextBackOff() time.Duration {
select {
case <-b.ctx.Done():
return Stop
default:
}
next := b.BackOff.NextBackOff()
if deadline, ok := b.ctx.Deadline(); ok && deadline.Sub(time.Now()) < next {
return Stop
}
return next
}

153
vendor/github.com/cenkalti/backoff/exponential.go generated vendored Normal file
View File

@ -0,0 +1,153 @@
package backoff
import (
"math/rand"
"time"
)
/*
ExponentialBackOff is a backoff implementation that increases the backoff
period for each retry attempt using a randomization function that grows exponentially.
NextBackOff() is calculated using the following formula:
randomized interval =
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
In other words NextBackOff() will range between the randomization factor
percentage below and above the retry interval.
For example, given the following parameters:
RetryInterval = 2
RandomizationFactor = 0.5
Multiplier = 2
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
multiplied by the exponential, that is, between 2 and 6 seconds.
Note: MaxInterval caps the RetryInterval and not the randomized interval.
If the time elapsed since an ExponentialBackOff instance is created goes past the
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
The elapsed time can be reset by calling Reset().
Example: Given the following default arguments, for 10 tries the sequence will be,
and assuming we go over the MaxElapsedTime on the 10th try:
Request # RetryInterval (seconds) Randomized Interval (seconds)
1 0.5 [0.25, 0.75]
2 0.75 [0.375, 1.125]
3 1.125 [0.562, 1.687]
4 1.687 [0.8435, 2.53]
5 2.53 [1.265, 3.795]
6 3.795 [1.897, 5.692]
7 5.692 [2.846, 8.538]
8 8.538 [4.269, 12.807]
9 12.807 [6.403, 19.210]
10 19.210 backoff.Stop
Note: Implementation is not thread-safe.
*/
type ExponentialBackOff struct {
InitialInterval time.Duration
RandomizationFactor float64
Multiplier float64
MaxInterval time.Duration
// After MaxElapsedTime the ExponentialBackOff stops.
// It never stops if MaxElapsedTime == 0.
MaxElapsedTime time.Duration
Clock Clock
currentInterval time.Duration
startTime time.Time
}
// Clock is an interface that returns current time for BackOff.
type Clock interface {
Now() time.Time
}
// Default values for ExponentialBackOff.
const (
DefaultInitialInterval = 500 * time.Millisecond
DefaultRandomizationFactor = 0.5
DefaultMultiplier = 1.5
DefaultMaxInterval = 60 * time.Second
DefaultMaxElapsedTime = 15 * time.Minute
)
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
func NewExponentialBackOff() *ExponentialBackOff {
b := &ExponentialBackOff{
InitialInterval: DefaultInitialInterval,
RandomizationFactor: DefaultRandomizationFactor,
Multiplier: DefaultMultiplier,
MaxInterval: DefaultMaxInterval,
MaxElapsedTime: DefaultMaxElapsedTime,
Clock: SystemClock,
}
b.Reset()
return b
}
type systemClock struct{}
func (t systemClock) Now() time.Time {
return time.Now()
}
// SystemClock implements Clock interface that uses time.Now().
var SystemClock = systemClock{}
// Reset the interval back to the initial retry interval and restarts the timer.
func (b *ExponentialBackOff) Reset() {
b.currentInterval = b.InitialInterval
b.startTime = b.Clock.Now()
}
// NextBackOff calculates the next backoff interval using the formula:
// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
func (b *ExponentialBackOff) NextBackOff() time.Duration {
// Make sure we have not gone over the maximum elapsed time.
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
return Stop
}
defer b.incrementCurrentInterval()
return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
}
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
// is created and is reset when Reset() is called.
//
// The elapsed time is computed using time.Now().UnixNano(). It is
// safe to call even while the backoff policy is used by a running
// ticker.
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
return b.Clock.Now().Sub(b.startTime)
}
// Increments the current interval by multiplying it with the multiplier.
func (b *ExponentialBackOff) incrementCurrentInterval() {
// Check for overflow, if overflow is detected set the current interval to the max interval.
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
b.currentInterval = b.MaxInterval
} else {
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
}
}
// Returns a random value from the following interval:
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
var delta = randomizationFactor * float64(currentInterval)
var minInterval = float64(currentInterval) - delta
var maxInterval = float64(currentInterval) + delta
// Get a random value from the range [minInterval, maxInterval].
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
// we want a 33% chance for selecting either 1, 2 or 3.
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
}

82
vendor/github.com/cenkalti/backoff/retry.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
package backoff
import "time"
// An Operation is executing by Retry() or RetryNotify().
// The operation will be retried using a backoff policy if it returns an error.
type Operation func() error
// Notify is a notify-on-error function. It receives an operation error and
// backoff delay if the operation failed (with an error).
//
// NOTE that if the backoff policy stated to stop retrying,
// the notify function isn't called.
type Notify func(error, time.Duration)
// Retry the operation o until it does not return error or BackOff stops.
// o is guaranteed to be run at least once.
//
// If o returns a *PermanentError, the operation is not retried, and the
// wrapped error is returned.
//
// Retry sleeps the goroutine for the duration returned by BackOff after a
// failed operation returns.
func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
// RetryNotify calls notify function with the error and wait duration
// for each failed attempt before sleep.
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
var err error
var next time.Duration
var t *time.Timer
cb := ensureContext(b)
b.Reset()
for {
if err = operation(); err == nil {
return nil
}
if permanent, ok := err.(*PermanentError); ok {
return permanent.Err
}
if next = cb.NextBackOff(); next == Stop {
return err
}
if notify != nil {
notify(err, next)
}
if t == nil {
t = time.NewTimer(next)
defer t.Stop()
} else {
t.Reset(next)
}
select {
case <-cb.Context().Done():
return err
case <-t.C:
}
}
}
// PermanentError signals that the operation should not be retried.
type PermanentError struct {
Err error
}
func (e *PermanentError) Error() string {
return e.Err.Error()
}
// Permanent wraps the given err in a *PermanentError.
func Permanent(err error) *PermanentError {
return &PermanentError{
Err: err,
}
}

82
vendor/github.com/cenkalti/backoff/ticker.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
package backoff
import (
"sync"
"time"
)
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
//
// Ticks will continue to arrive when the previous operation is still running,
// so operations that take a while to fail could run in quick succession.
type Ticker struct {
C <-chan time.Time
c chan time.Time
b BackOffContext
stop chan struct{}
stopOnce sync.Once
}
// NewTicker returns a new Ticker containing a channel that will send
// the time at times specified by the BackOff argument. Ticker is
// guaranteed to tick at least once. The channel is closed when Stop
// method is called or BackOff stops. It is not safe to manipulate the
// provided backoff policy (notably calling NextBackOff or Reset)
// while the ticker is running.
func NewTicker(b BackOff) *Ticker {
c := make(chan time.Time)
t := &Ticker{
C: c,
c: c,
b: ensureContext(b),
stop: make(chan struct{}),
}
t.b.Reset()
go t.run()
return t
}
// Stop turns off a ticker. After Stop, no more ticks will be sent.
func (t *Ticker) Stop() {
t.stopOnce.Do(func() { close(t.stop) })
}
func (t *Ticker) run() {
c := t.c
defer close(c)
// Ticker is guaranteed to tick at least once.
afterC := t.send(time.Now())
for {
if afterC == nil {
return
}
select {
case tick := <-afterC:
afterC = t.send(tick)
case <-t.stop:
t.c = nil // Prevent future ticks from being sent to the channel.
return
case <-t.b.Context().Done():
return
}
}
}
func (t *Ticker) send(tick time.Time) <-chan time.Time {
select {
case t.c <- tick:
case <-t.stop:
return nil
}
next := t.b.NextBackOff()
if next == Stop {
t.Stop()
return nil
}
return time.After(next)
}

35
vendor/github.com/cenkalti/backoff/tries.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
package backoff
import "time"
/*
WithMaxRetries creates a wrapper around another BackOff, which will
return Stop if NextBackOff() has been called too many times since
the last time Reset() was called
Note: Implementation is not thread-safe.
*/
func WithMaxRetries(b BackOff, max uint64) BackOff {
return &backOffTries{delegate: b, maxTries: max}
}
type backOffTries struct {
delegate BackOff
maxTries uint64
numTries uint64
}
func (b *backOffTries) NextBackOff() time.Duration {
if b.maxTries > 0 {
if b.maxTries <= b.numTries {
return Stop
}
b.numTries++
}
return b.delegate.NextBackOff()
}
func (b *backOffTries) Reset() {
b.numTries = 0
b.delegate.Reset()
}

69
vendor/github.com/go-acme/lego/acme/api/account.go generated vendored Normal file
View File

@ -0,0 +1,69 @@
package api
import (
"encoding/base64"
"errors"
"fmt"
"github.com/go-acme/lego/acme"
)
type AccountService service
// New Creates a new account.
func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
var account acme.Account
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
location := getLocation(resp)
if len(location) > 0 {
a.core.jws.SetKid(location)
}
if err != nil {
return acme.ExtendedAccount{Location: location}, err
}
return acme.ExtendedAccount{Account: account, Location: location}, nil
}
// NewEAB Creates a new account with an External Account Binding.
func (a *AccountService) NewEAB(accMsg acme.Account, kid string, hmacEncoded string) (acme.ExtendedAccount, error) {
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
if err != nil {
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %v", err)
}
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
if err != nil {
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %v", err)
}
accMsg.ExternalAccountBinding = eabJWS
return a.New(accMsg)
}
// Get Retrieves an account.
func (a *AccountService) Get(accountURL string) (acme.Account, error) {
if len(accountURL) == 0 {
return acme.Account{}, errors.New("account[get]: empty URL")
}
var account acme.Account
_, err := a.core.post(accountURL, acme.Account{}, &account)
if err != nil {
return acme.Account{}, err
}
return account, nil
}
// Deactivate Deactivates an account.
func (a *AccountService) Deactivate(accountURL string) error {
if len(accountURL) == 0 {
return errors.New("account[deactivate]: empty URL")
}
req := acme.Account{Status: acme.StatusDeactivated}
_, err := a.core.post(accountURL, req, nil)
return err
}

166
vendor/github.com/go-acme/lego/acme/api/api.go generated vendored Normal file
View File

@ -0,0 +1,166 @@
package api
import (
"bytes"
"context"
"crypto"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/cenkalti/backoff"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api/internal/nonces"
"github.com/go-acme/lego/acme/api/internal/secure"
"github.com/go-acme/lego/acme/api/internal/sender"
"github.com/go-acme/lego/log"
)
// Core ACME/LE core API.
type Core struct {
doer *sender.Doer
nonceManager *nonces.Manager
jws *secure.JWS
directory acme.Directory
HTTPClient *http.Client
common service // Reuse a single struct instead of allocating one for each service on the heap.
Accounts *AccountService
Authorizations *AuthorizationService
Certificates *CertificateService
Challenges *ChallengeService
Orders *OrderService
}
// New Creates a new Core.
func New(httpClient *http.Client, userAgent string, caDirURL, kid string, privateKey crypto.PrivateKey) (*Core, error) {
doer := sender.NewDoer(httpClient, userAgent)
dir, err := getDirectory(doer, caDirURL)
if err != nil {
return nil, err
}
nonceManager := nonces.NewManager(doer, dir.NewNonceURL)
jws := secure.NewJWS(privateKey, kid, nonceManager)
c := &Core{doer: doer, nonceManager: nonceManager, jws: jws, directory: dir, HTTPClient: httpClient}
c.common.core = c
c.Accounts = (*AccountService)(&c.common)
c.Authorizations = (*AuthorizationService)(&c.common)
c.Certificates = (*CertificateService)(&c.common)
c.Challenges = (*ChallengeService)(&c.common)
c.Orders = (*OrderService)(&c.common)
return c, nil
}
// post performs an HTTP POST request and parses the response body as JSON,
// into the provided respBody object.
func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, error) {
content, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.New("failed to marshal message")
}
return a.retrievablePost(uri, content, response)
}
// postAsGet performs an HTTP POST ("POST-as-GET") request.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
return a.retrievablePost(uri, []byte{}, response)
}
func (a *Core) retrievablePost(uri string, content []byte, response interface{}) (*http.Response, error) {
// during tests, allow to support ~90% of bad nonce with a minimum of attempts.
bo := backoff.NewExponentialBackOff()
bo.InitialInterval = 200 * time.Millisecond
bo.MaxInterval = 5 * time.Second
bo.MaxElapsedTime = 20 * time.Second
ctx, cancel := context.WithCancel(context.Background())
var resp *http.Response
operation := func() error {
var err error
resp, err = a.signedPost(uri, content, response)
if err != nil {
switch err.(type) {
// Retry if the nonce was invalidated
case *acme.NonceError:
log.Infof("nonce error retry: %s", err)
return err
default:
cancel()
return err
}
}
return nil
}
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
if err != nil {
return nil, err
}
return resp, nil
}
func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) {
signedContent, err := a.jws.SignContent(uri, content)
if err != nil {
return nil, fmt.Errorf("failed to post JWS message -> failed to sign content -> %v", err)
}
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
// nonceErr is ignored to keep the root error.
nonce, nonceErr := nonces.GetFromResponse(resp)
if nonceErr == nil {
a.nonceManager.Push(nonce)
}
return resp, err
}
func (a *Core) signEABContent(newAccountURL, kid string, hmac []byte) ([]byte, error) {
eabJWS, err := a.jws.SignEABContent(newAccountURL, kid, hmac)
if err != nil {
return nil, err
}
return []byte(eabJWS.FullSerialize()), nil
}
// GetKeyAuthorization Gets the key authorization
func (a *Core) GetKeyAuthorization(token string) (string, error) {
return a.jws.GetKeyAuthorization(token)
}
func (a *Core) GetDirectory() acme.Directory {
return a.directory
}
func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) {
var dir acme.Directory
if _, err := do.Get(caDirURL, &dir); err != nil {
return dir, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
}
if dir.NewAccountURL == "" {
return dir, errors.New("directory missing new registration URL")
}
if dir.NewOrderURL == "" {
return dir, errors.New("directory missing new order URL")
}
return dir, nil
}

View File

@ -0,0 +1,34 @@
package api
import (
"errors"
"github.com/go-acme/lego/acme"
)
type AuthorizationService service
// Get Gets an authorization.
func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error) {
if len(authzURL) == 0 {
return acme.Authorization{}, errors.New("authorization[get]: empty URL")
}
var authz acme.Authorization
_, err := c.core.postAsGet(authzURL, &authz)
if err != nil {
return acme.Authorization{}, err
}
return authz, nil
}
// Deactivate Deactivates an authorization.
func (c *AuthorizationService) Deactivate(authzURL string) error {
if len(authzURL) == 0 {
return errors.New("authorization[deactivate]: empty URL")
}
var disabledAuth acme.Authorization
_, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth)
return err
}

99
vendor/github.com/go-acme/lego/acme/api/certificate.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package api
import (
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"net/http"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/log"
)
// maxBodySize is the maximum size of body that we will read.
const maxBodySize = 1024 * 1024
type CertificateService service
// Get Returns the certificate and the issuer certificate.
// 'bundle' is only applied if the issuer is provided by the 'up' link.
func (c *CertificateService) Get(certURL string, bundle bool) ([]byte, []byte, error) {
cert, up, err := c.get(certURL)
if err != nil {
return nil, nil, err
}
// Get issuerCert from bundled response from Let's Encrypt
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
_, issuer := pem.Decode(cert)
if issuer != nil {
return cert, issuer, nil
}
issuer, err = c.getIssuerFromLink(up)
if err != nil {
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err)
} else if len(issuer) > 0 {
// If bundle is true, we want to return a certificate bundle.
// To do this, we append the issuer cert to the issued cert.
if bundle {
cert = append(cert, issuer...)
}
}
return cert, issuer, nil
}
// Revoke Revokes a certificate.
func (c *CertificateService) Revoke(req acme.RevokeCertMessage) error {
_, err := c.core.post(c.core.GetDirectory().RevokeCertURL, req, nil)
return err
}
// get Returns the certificate and the "up" link.
func (c *CertificateService) get(certURL string) ([]byte, string, error) {
if len(certURL) == 0 {
return nil, "", errors.New("certificate[get]: empty URL")
}
resp, err := c.core.postAsGet(certURL, nil)
if err != nil {
return nil, "", err
}
cert, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if err != nil {
return nil, "", err
}
// The issuer certificate link may be supplied via an "up" link
// in the response headers of a new certificate.
// See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
up := getLink(resp.Header, "up")
return cert, up, err
}
// getIssuerFromLink requests the issuer certificate
func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) {
if len(up) == 0 {
return nil, nil
}
log.Infof("acme: Requesting issuer cert from %s", up)
cert, _, err := c.get(up)
if err != nil {
return nil, err
}
_, err = x509.ParseCertificate(cert)
if err != nil {
return nil, err
}
return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert)), nil
}

45
vendor/github.com/go-acme/lego/acme/api/challenge.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
package api
import (
"errors"
"github.com/go-acme/lego/acme"
)
type ChallengeService service
// New Creates a challenge.
func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) {
if len(chlgURL) == 0 {
return acme.ExtendedChallenge{}, errors.New("challenge[new]: empty URL")
}
// Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`.
// We use an empty struct instance as the postJSON payload here to achieve this result.
var chlng acme.ExtendedChallenge
resp, err := c.core.post(chlgURL, struct{}{}, &chlng)
if err != nil {
return acme.ExtendedChallenge{}, err
}
chlng.AuthorizationURL = getLink(resp.Header, "up")
chlng.RetryAfter = getRetryAfter(resp)
return chlng, nil
}
// Get Gets a challenge.
func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) {
if len(chlgURL) == 0 {
return acme.ExtendedChallenge{}, errors.New("challenge[get]: empty URL")
}
var chlng acme.ExtendedChallenge
resp, err := c.core.postAsGet(chlgURL, &chlng)
if err != nil {
return acme.ExtendedChallenge{}, err
}
chlng.AuthorizationURL = getLink(resp.Header, "up")
chlng.RetryAfter = getRetryAfter(resp)
return chlng, nil
}

View File

@ -0,0 +1,78 @@
package nonces
import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/go-acme/lego/acme/api/internal/sender"
)
// Manager Manages nonces.
type Manager struct {
do *sender.Doer
nonceURL string
nonces []string
sync.Mutex
}
// NewManager Creates a new Manager.
func NewManager(do *sender.Doer, nonceURL string) *Manager {
return &Manager{
do: do,
nonceURL: nonceURL,
}
}
// Pop Pops a nonce.
func (n *Manager) Pop() (string, bool) {
n.Lock()
defer n.Unlock()
if len(n.nonces) == 0 {
return "", false
}
nonce := n.nonces[len(n.nonces)-1]
n.nonces = n.nonces[:len(n.nonces)-1]
return nonce, true
}
// Push Pushes a nonce.
func (n *Manager) Push(nonce string) {
n.Lock()
defer n.Unlock()
n.nonces = append(n.nonces, nonce)
}
// Nonce implement jose.NonceSource
func (n *Manager) Nonce() (string, error) {
if nonce, ok := n.Pop(); ok {
return nonce, nil
}
return n.getNonce()
}
func (n *Manager) getNonce() (string, error) {
resp, err := n.do.Head(n.nonceURL)
if err != nil {
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %v", err)
}
return GetFromResponse(resp)
}
// GetFromResponse Extracts a nonce from a HTTP response.
func GetFromResponse(resp *http.Response) (string, error) {
if resp == nil {
return "", errors.New("nil response")
}
nonce := resp.Header.Get("Replay-Nonce")
if nonce == "" {
return "", fmt.Errorf("server did not respond with a proper nonce header")
}
return nonce, nil
}

View File

@ -0,0 +1,130 @@
package secure
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/base64"
"fmt"
"github.com/go-acme/lego/acme/api/internal/nonces"
jose "gopkg.in/square/go-jose.v2"
)
// JWS Represents a JWS.
type JWS struct {
privKey crypto.PrivateKey
kid string // Key identifier
nonces *nonces.Manager
}
// NewJWS Create a new JWS.
func NewJWS(privateKey crypto.PrivateKey, kid string, nonceManager *nonces.Manager) *JWS {
return &JWS{
privKey: privateKey,
nonces: nonceManager,
kid: kid,
}
}
// SetKid Sets a key identifier.
func (j *JWS) SetKid(kid string) {
j.kid = kid
}
// SignContent Signs a content with the JWS.
func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
var alg jose.SignatureAlgorithm
switch k := j.privKey.(type) {
case *rsa.PrivateKey:
alg = jose.RS256
case *ecdsa.PrivateKey:
if k.Curve == elliptic.P256() {
alg = jose.ES256
} else if k.Curve == elliptic.P384() {
alg = jose.ES384
}
}
signKey := jose.SigningKey{
Algorithm: alg,
Key: jose.JSONWebKey{Key: j.privKey, KeyID: j.kid},
}
options := jose.SignerOptions{
NonceSource: j.nonces,
ExtraHeaders: map[jose.HeaderKey]interface{}{
"url": url,
},
}
if j.kid == "" {
options.EmbedJWK = true
}
signer, err := jose.NewSigner(signKey, &options)
if err != nil {
return nil, fmt.Errorf("failed to create jose signer -> %v", err)
}
signed, err := signer.Sign(content)
if err != nil {
return nil, fmt.Errorf("failed to sign content -> %v", err)
}
return signed, nil
}
// SignEABContent Signs an external account binding content with the JWS.
func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
jwk := jose.JSONWebKey{Key: j.privKey}
jwkJSON, err := jwk.Public().MarshalJSON()
if err != nil {
return nil, fmt.Errorf("acme: error encoding eab jwk key: %v", err)
}
signer, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
&jose.SignerOptions{
EmbedJWK: false,
ExtraHeaders: map[jose.HeaderKey]interface{}{
"kid": kid,
"url": url,
},
},
)
if err != nil {
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %v", err)
}
signed, err := signer.Sign(jwkJSON)
if err != nil {
return nil, fmt.Errorf("failed to External Account Binding sign content -> %v", err)
}
return signed, nil
}
// GetKeyAuthorization Gets the key authorization for a token.
func (j *JWS) GetKeyAuthorization(token string) (string, error) {
var publicKey crypto.PublicKey
switch k := j.privKey.(type) {
case *ecdsa.PrivateKey:
publicKey = k.Public()
case *rsa.PrivateKey:
publicKey = k.Public()
}
// Generate the Key Authorization for the challenge
jwk := &jose.JSONWebKey{Key: publicKey}
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
if err != nil {
return "", err
}
// unpad the base64URL
keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
return token + "." + keyThumb, nil
}

View File

@ -0,0 +1,146 @@
package sender
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"runtime"
"strings"
"github.com/go-acme/lego/acme"
)
type RequestOption func(*http.Request) error
func contentType(ct string) RequestOption {
return func(req *http.Request) error {
req.Header.Set("Content-Type", ct)
return nil
}
}
type Doer struct {
httpClient *http.Client
userAgent string
}
// NewDoer Creates a new Doer.
func NewDoer(client *http.Client, userAgent string) *Doer {
return &Doer{
httpClient: client,
userAgent: userAgent,
}
}
// Get performs a GET request with a proper User-Agent string.
// If "response" is not provided, callers should close resp.Body when done reading from it.
func (d *Doer) Get(url string, response interface{}) (*http.Response, error) {
req, err := d.newRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return d.do(req, response)
}
// Head performs a HEAD request with a proper User-Agent string.
// The response body (resp.Body) is already closed when this function returns.
func (d *Doer) Head(url string) (*http.Response, error) {
req, err := d.newRequest(http.MethodHead, url, nil)
if err != nil {
return nil, err
}
return d.do(req, nil)
}
// Post performs a POST request with a proper User-Agent string.
// If "response" is not provided, callers should close resp.Body when done reading from it.
func (d *Doer) Post(url string, body io.Reader, bodyType string, response interface{}) (*http.Response, error) {
req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType))
if err != nil {
return nil, err
}
return d.do(req, response)
}
func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
req, err := http.NewRequest(method, uri, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("User-Agent", d.formatUserAgent())
for _, opt := range opts {
err = opt(req)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
}
return req, nil
}
func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, error) {
resp, err := d.httpClient.Do(req)
if err != nil {
return nil, err
}
if err = checkError(req, resp); err != nil {
return resp, err
}
if response != nil {
raw, err := ioutil.ReadAll(resp.Body)
if err != nil {
return resp, err
}
defer resp.Body.Close()
err = json.Unmarshal(raw, response)
if err != nil {
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %v", raw, response, err)
}
}
return resp, nil
}
// formatUserAgent builds and returns the User-Agent string to use in requests.
func (d *Doer) formatUserAgent() string {
ua := fmt.Sprintf("%s %s (%s; %s; %s)", d.userAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
return strings.TrimSpace(ua)
}
func checkError(req *http.Request, resp *http.Response) error {
if resp.StatusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%d :: %s :: %s :: %v", resp.StatusCode, req.Method, req.URL, err)
}
var errorDetails *acme.ProblemDetails
err = json.Unmarshal(body, &errorDetails)
if err != nil {
return fmt.Errorf("%d ::%s :: %s :: %v :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
}
errorDetails.Method = req.Method
errorDetails.URL = req.URL.String()
// Check for errors we handle specifically
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
return &acme.NonceError{ProblemDetails: errorDetails}
}
return errorDetails
}
return nil
}

View File

@ -0,0 +1,14 @@
package sender
// CODE GENERATED AUTOMATICALLY
// THIS FILE MUST NOT BE EDITED BY HAND
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/2.7.1"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "detach"
)

65
vendor/github.com/go-acme/lego/acme/api/order.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package api
import (
"encoding/base64"
"errors"
"github.com/go-acme/lego/acme"
)
type OrderService service
// New Creates a new order.
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
var identifiers []acme.Identifier
for _, domain := range domains {
identifiers = append(identifiers, acme.Identifier{Type: "dns", Value: domain})
}
orderReq := acme.Order{Identifiers: identifiers}
var order acme.Order
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
if err != nil {
return acme.ExtendedOrder{}, err
}
return acme.ExtendedOrder{
Location: resp.Header.Get("Location"),
Order: order,
}, nil
}
// Get Gets an order.
func (o *OrderService) Get(orderURL string) (acme.Order, error) {
if len(orderURL) == 0 {
return acme.Order{}, errors.New("order[get]: empty URL")
}
var order acme.Order
_, err := o.core.postAsGet(orderURL, &order)
if err != nil {
return acme.Order{}, err
}
return order, nil
}
// UpdateForCSR Updates an order for a CSR.
func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.Order, error) {
csrMsg := acme.CSRMessage{
Csr: base64.RawURLEncoding.EncodeToString(csr),
}
var order acme.Order
_, err := o.core.post(orderURL, csrMsg, &order)
if err != nil {
return acme.Order{}, err
}
if order.Status == acme.StatusInvalid {
return acme.Order{}, order.Error
}
return order, nil
}

45
vendor/github.com/go-acme/lego/acme/api/service.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
package api
import (
"net/http"
"regexp"
)
type service struct {
core *Core
}
// getLink get a rel into the Link header
func getLink(header http.Header, rel string) string {
var linkExpr = regexp.MustCompile(`<(.+?)>;\s*rel="(.+?)"`)
for _, link := range header["Link"] {
for _, m := range linkExpr.FindAllStringSubmatch(link, -1) {
if len(m) != 3 {
continue
}
if m[2] == rel {
return m[1]
}
}
}
return ""
}
// getLocation get the value of the header Location
func getLocation(resp *http.Response) string {
if resp == nil {
return ""
}
return resp.Header.Get("Location")
}
// getRetryAfter get the value of the header Retry-After
func getRetryAfter(resp *http.Response) string {
if resp == nil {
return ""
}
return resp.Header.Get("Retry-After")
}

284
vendor/github.com/go-acme/lego/acme/commons.go generated vendored Normal file
View File

@ -0,0 +1,284 @@
// Package acme contains all objects related the ACME endpoints.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16
package acme
import (
"encoding/json"
"time"
)
// Challenge statuses
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.6
const (
StatusPending = "pending"
StatusInvalid = "invalid"
StatusValid = "valid"
StatusProcessing = "processing"
StatusDeactivated = "deactivated"
StatusExpired = "expired"
StatusRevoked = "revoked"
)
// Directory the ACME directory object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
type Directory struct {
NewNonceURL string `json:"newNonce"`
NewAccountURL string `json:"newAccount"`
NewOrderURL string `json:"newOrder"`
NewAuthzURL string `json:"newAuthz"`
RevokeCertURL string `json:"revokeCert"`
KeyChangeURL string `json:"keyChange"`
Meta Meta `json:"meta"`
}
// Meta the ACME meta object (related to Directory).
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
type Meta struct {
// termsOfService (optional, string):
// A URL identifying the current terms of service.
TermsOfService string `json:"termsOfService"`
// website (optional, string):
// An HTTP or HTTPS URL locating a website providing more information about the ACME server.
Website string `json:"website"`
// caaIdentities (optional, array of string):
// The hostnames that the ACME server recognizes as referring to itself
// for the purposes of CAA record validation as defined in [RFC6844].
// Each string MUST represent the same sequence of ASCII code points
// that the server will expect to see as the "Issuer Domain Name" in a CAA issue or issuewild property tag.
// This allows clients to determine the correct issuer domain name to use when configuring CAA records.
CaaIdentities []string `json:"caaIdentities"`
// externalAccountRequired (optional, boolean):
// If this field is present and set to "true",
// then the CA requires that all new- account requests include an "externalAccountBinding" field
// associating the new account with an external account.
ExternalAccountRequired bool `json:"externalAccountRequired"`
}
// ExtendedAccount a extended Account.
type ExtendedAccount struct {
Account
// Contains the value of the response header `Location`
Location string `json:"-"`
}
// Account the ACME account Object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.2
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3
type Account struct {
// status (required, string):
// The status of this account.
// Possible values are: "valid", "deactivated", and "revoked".
// The value "deactivated" should be used to indicate client-initiated deactivation
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
Status string `json:"status,omitempty"`
// contact (optional, array of string):
// An array of URLs that the server can use to contact the client for issues related to this account.
// For example, the server may wish to notify the client about server-initiated revocation or certificate expiration.
// For information on supported URL schemes, see Section 7.3
Contact []string `json:"contact,omitempty"`
// termsOfServiceAgreed (optional, boolean):
// Including this field in a new-account request,
// with a value of true, indicates the client's agreement with the terms of service.
// This field is not updateable by the client.
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
// orders (required, string):
// A URL from which a list of orders submitted by this account can be fetched via a POST-as-GET request,
// as described in Section 7.1.2.1.
Orders string `json:"orders,omitempty"`
// onlyReturnExisting (optional, boolean):
// If this field is present with the value "true",
// then the server MUST NOT create a new account if one does not already exist.
// This allows a client to look up an account URL based on an account key (see Section 7.3.1).
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
// externalAccountBinding (optional, object):
// An optional field for binding the new account with an existing non-ACME account (see Section 7.3.4).
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
}
// ExtendedOrder a extended Order.
type ExtendedOrder struct {
Order
// The order URL, contains the value of the response header `Location`
Location string `json:"-"`
}
// Order the ACME order Object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.3
type Order struct {
// status (required, string):
// The status of this order.
// Possible values are: "pending", "ready", "processing", "valid", and "invalid".
Status string `json:"status,omitempty"`
// expires (optional, string):
// The timestamp after which the server will consider this order invalid,
// encoded in the format specified in RFC 3339 [RFC3339].
// This field is REQUIRED for objects with "pending" or "valid" in the status field.
Expires string `json:"expires,omitempty"`
// identifiers (required, array of object):
// An array of identifier objects that the order pertains to.
Identifiers []Identifier `json:"identifiers"`
// notBefore (optional, string):
// The requested value of the notBefore field in the certificate,
// in the date format defined in [RFC3339].
NotBefore string `json:"notBefore,omitempty"`
// notAfter (optional, string):
// The requested value of the notAfter field in the certificate,
// in the date format defined in [RFC3339].
NotAfter string `json:"notAfter,omitempty"`
// error (optional, object):
// The error that occurred while processing the order, if any.
// This field is structured as a problem document [RFC7807].
Error *ProblemDetails `json:"error,omitempty"`
// authorizations (required, array of string):
// For pending orders,
// the authorizations that the client needs to complete before the requested certificate can be issued (see Section 7.5),
// including unexpired authorizations that the client has completed in the past for identifiers specified in the order.
// The authorizations required are dictated by server policy
// and there may not be a 1:1 relationship between the order identifiers and the authorizations required.
// For final orders (in the "valid" or "invalid" state), the authorizations that were completed.
// Each entry is a URL from which an authorization can be fetched with a POST-as-GET request.
Authorizations []string `json:"authorizations,omitempty"`
// finalize (required, string):
// A URL that a CSR must be POSTed to once all of the order's authorizations are satisfied to finalize the order.
// The result of a successful finalization will be the population of the certificate URL for the order.
Finalize string `json:"finalize,omitempty"`
// certificate (optional, string):
// A URL for the certificate that has been issued in response to this order
Certificate string `json:"certificate,omitempty"`
}
// Authorization the ACME authorization object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
type Authorization struct {
// status (required, string):
// The status of this authorization.
// Possible values are: "pending", "valid", "invalid", "deactivated", "expired", and "revoked".
Status string `json:"status"`
// expires (optional, string):
// The timestamp after which the server will consider this authorization invalid,
// encoded in the format specified in RFC 3339 [RFC3339].
// This field is REQUIRED for objects with "valid" in the "status" field.
Expires time.Time `json:"expires,omitempty"`
// identifier (required, object):
// The identifier that the account is authorized to represent
Identifier Identifier `json:"identifier,omitempty"`
// challenges (required, array of objects):
// For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier.
// For valid authorizations, the challenge that was validated.
// For invalid authorizations, the challenge that was attempted and failed.
// Each array entry is an object with parameters required to validate the challenge.
// A client should attempt to fulfill one of these challenges,
// and a server should consider any one of the challenges sufficient to make the authorization valid.
Challenges []Challenge `json:"challenges,omitempty"`
// wildcard (optional, boolean):
// For authorizations created as a result of a newOrder request containing a DNS identifier
// with a value that contained a wildcard prefix this field MUST be present, and true.
Wildcard bool `json:"wildcard,omitempty"`
}
// ExtendedChallenge a extended Challenge.
type ExtendedChallenge struct {
Challenge
// Contains the value of the response header `Retry-After`
RetryAfter string `json:"-"`
// Contains the value of the response header `Link` rel="up"
AuthorizationURL string `json:"-"`
}
// Challenge the ACME challenge object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.5
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8
type Challenge struct {
// type (required, string):
// The type of challenge encoded in the object.
Type string `json:"type"`
// url (required, string):
// The URL to which a response can be posted.
URL string `json:"url"`
// status (required, string):
// The status of this challenge. Possible values are: "pending", "processing", "valid", and "invalid".
Status string `json:"status"`
// validated (optional, string):
// The time at which the server validated this challenge,
// encoded in the format specified in RFC 3339 [RFC3339].
// This field is REQUIRED if the "status" field is "valid".
Validated time.Time `json:"validated,omitempty"`
// error (optional, object):
// Error that occurred while the server was validating the challenge, if any,
// structured as a problem document [RFC7807].
// Multiple errors can be indicated by using subproblems Section 6.7.1.
// A challenge object with an error MUST have status equal to "invalid".
Error *ProblemDetails `json:"error,omitempty"`
// token (required, string):
// A random value that uniquely identifies the challenge.
// This value MUST have at least 128 bits of entropy.
// It MUST NOT contain any characters outside the base64url alphabet,
// and MUST NOT include base64 padding characters ("=").
// See [RFC4086] for additional information on randomness requirements.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
Token string `json:"token"`
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.1
KeyAuthorization string `json:"keyAuthorization"`
}
// Identifier the ACME identifier object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-9.7.7
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
// CSRMessage Certificate Signing Request
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.4
type CSRMessage struct {
// csr (required, string):
// A CSR encoding the parameters for the certificate being requested [RFC2986].
// The CSR is sent in the base64url-encoded version of the DER format.
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.).
Csr string `json:"csr"`
}
// RevokeCertMessage a certificate revocation message
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.6
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
type RevokeCertMessage struct {
// certificate (required, string):
// The certificate to be revoked, in the base64url-encoded version of the DER format.
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.)
Certificate string `json:"certificate"`
// reason (optional, int):
// One of the revocation reasonCodes defined in Section 5.3.1 of [RFC5280] to be used when generating OCSP responses and CRLs.
// If this field is not set the server SHOULD omit the reasonCode CRL entry extension when generating OCSP responses and CRLs.
// The server MAY disallow a subset of reasonCodes from being used by the user.
// If a request contains a disallowed reasonCode the server MUST reject it with the error type "urn:ietf:params:acme:error:badRevocationReason".
// The problem document detail SHOULD indicate which reasonCodes are allowed.
Reason *uint `json:"reason,omitempty"`
}

58
vendor/github.com/go-acme/lego/acme/errors.go generated vendored Normal file
View File

@ -0,0 +1,58 @@
package acme
import (
"fmt"
)
// Errors types
const (
errNS = "urn:ietf:params:acme:error:"
BadNonceErr = errNS + "badNonce"
)
// ProblemDetails the problem details object
// - https://tools.ietf.org/html/rfc7807#section-3.1
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3.3
type ProblemDetails struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
HTTPStatus int `json:"status,omitempty"`
Instance string `json:"instance,omitempty"`
SubProblems []SubProblem `json:"subproblems,omitempty"`
// additional values to have a better error message (Not defined by the RFC)
Method string `json:"method,omitempty"`
URL string `json:"url,omitempty"`
}
// SubProblem a "subproblems"
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.7.1
type SubProblem struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
Identifier Identifier `json:"identifier,omitempty"`
}
func (p ProblemDetails) Error() string {
msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus)
if len(p.Method) != 0 || len(p.URL) != 0 {
msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)
}
msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)
for _, sub := range p.SubProblems {
msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)
}
if len(p.Instance) == 0 {
msg += ", url: " + p.Instance
}
return msg
}
// NonceError represents the error which is returned
// if the nonce sent by the client was not accepted by the server.
type NonceError struct {
*ProblemDetails
}

256
vendor/github.com/go-acme/lego/certcrypto/crypto.go generated vendored Normal file
View File

@ -0,0 +1,256 @@
package certcrypto
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"
"time"
"golang.org/x/crypto/ocsp"
)
// Constants for all key types we support.
const (
EC256 = KeyType("P256")
EC384 = KeyType("P384")
RSA2048 = KeyType("2048")
RSA4096 = KeyType("4096")
RSA8192 = KeyType("8192")
)
const (
// OCSPGood means that the certificate is valid.
OCSPGood = ocsp.Good
// OCSPRevoked means that the certificate has been deliberately revoked.
OCSPRevoked = ocsp.Revoked
// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
OCSPUnknown = ocsp.Unknown
// OCSPServerFailed means that the OCSP responder failed to process the request.
OCSPServerFailed = ocsp.ServerFailed
)
// Constants for OCSP must staple
var (
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
)
// KeyType represents the key algo as well as the key size or curve to use.
type KeyType string
type DERCertificateBytes []byte
// ParsePEMBundle parses a certificate bundle from top to bottom and returns
// a slice of x509 certificates. This function will error if no certificates are found.
func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
var certificates []*x509.Certificate
var certDERBlock *pem.Block
for {
certDERBlock, bundle = pem.Decode(bundle)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, cert)
}
}
if len(certificates) == 0 {
return nil, errors.New("no certificates were found while parsing the bundle")
}
return certificates, nil
}
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
keyBlock, _ := pem.Decode(key)
switch keyBlock.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(keyBlock.Bytes)
default:
return nil, errors.New("unknown PEM header value")
}
}
func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
switch keyType {
case EC256:
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case EC384:
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case RSA2048:
return rsa.GenerateKey(rand.Reader, 2048)
case RSA4096:
return rsa.GenerateKey(rand.Reader, 4096)
case RSA8192:
return rsa.GenerateKey(rand.Reader, 8192)
}
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
}
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
template := x509.CertificateRequest{
Subject: pkix.Name{CommonName: domain},
DNSNames: san,
}
if mustStaple {
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
Id: tlsFeatureExtensionOID,
Value: ocspMustStapleFeature,
})
}
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
}
func PEMEncode(data interface{}) []byte {
return pem.EncodeToMemory(PEMBlock(data))
}
func PEMBlock(data interface{}) *pem.Block {
var pemBlock *pem.Block
switch key := data.(type) {
case *ecdsa.PrivateKey:
keyBytes, _ := x509.MarshalECPrivateKey(key)
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
case *rsa.PrivateKey:
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
case *x509.CertificateRequest:
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
case DERCertificateBytes:
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(DERCertificateBytes))}
}
return pemBlock
}
func pemDecode(data []byte) (*pem.Block, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, fmt.Errorf("PEM decode did not yield a valid block. Is the certificate in the right format?")
}
return pemBlock, nil
}
func PemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
pemBlock, err := pemDecode(pem)
if pemBlock == nil {
return nil, err
}
if pemBlock.Type != "CERTIFICATE REQUEST" {
return nil, fmt.Errorf("PEM block is not a certificate request")
}
return x509.ParseCertificateRequest(pemBlock.Bytes)
}
// ParsePEMCertificate returns Certificate from a PEM encoded certificate.
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
pemBlock, err := pemDecode(cert)
if pemBlock == nil {
return nil, err
}
// from a DER encoded certificate
return x509.ParseCertificate(pemBlock.Bytes)
}
func ExtractDomains(cert *x509.Certificate) []string {
domains := []string{cert.Subject.CommonName}
// Check for SAN certificate
for _, sanDomain := range cert.DNSNames {
if sanDomain == cert.Subject.CommonName {
continue
}
domains = append(domains, sanDomain)
}
return domains
}
func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
domains := []string{csr.Subject.CommonName}
// loop over the SubjectAltName DNS names
for _, sanName := range csr.DNSNames {
if containsSAN(domains, sanName) {
// Duplicate; skip this name
continue
}
// Name is unique
domains = append(domains, sanName)
}
return domains
}
func containsSAN(domains []string, sanName string) bool {
for _, existingName := range domains {
if existingName == sanName {
return true
}
}
return false
}
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
}
func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
if expiration.IsZero() {
expiration = time.Now().Add(365)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "ACME Challenge TEMP",
},
NotBefore: time.Now(),
NotAfter: expiration,
KeyUsage: x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true,
DNSNames: []string{domain},
ExtraExtensions: extensions,
}
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
}

View File

@ -0,0 +1,69 @@
package certificate
import (
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/log"
)
const (
// overallRequestLimit is the overall number of request per second
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
// From the documentation the limitation is 20 requests per second,
// but using 20 as value doesn't work but 18 do
overallRequestLimit = 18
)
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
resc, errc := make(chan acme.Authorization), make(chan domainError)
delay := time.Second / overallRequestLimit
for _, authzURL := range order.Authorizations {
time.Sleep(delay)
go func(authzURL string) {
authz, err := c.core.Authorizations.Get(authzURL)
if err != nil {
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
return
}
resc <- authz
}(authzURL)
}
var responses []acme.Authorization
failures := make(obtainError)
for i := 0; i < len(order.Authorizations); i++ {
select {
case res := <-resc:
responses = append(responses, res)
case err := <-errc:
failures[err.Domain] = err.Error
}
}
for i, auth := range order.Authorizations {
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
}
close(resc)
close(errc)
// be careful to not return an empty failures map;
// even if empty, they become non-nil error values
if len(failures) > 0 {
return responses, failures
}
return responses, nil
}
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
for _, auth := range order.Authorizations {
if c.core.Authorizations.Deactivate(auth) != nil {
log.Infof("Unable to deactivate the authorization: %s", auth)
}
}
}

View File

@ -0,0 +1,522 @@
package certificate
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/platform/wait"
"golang.org/x/crypto/ocsp"
"golang.org/x/net/idna"
)
// maxBodySize is the maximum size of body that we will read.
const maxBodySize = 1024 * 1024
// Resource represents a CA issued certificate.
// PrivateKey, Certificate and IssuerCertificate are all
// already PEM encoded and can be directly written to disk.
// Certificate may be a certificate bundle,
// depending on the options supplied to create it.
type Resource struct {
Domain string `json:"domain"`
CertURL string `json:"certUrl"`
CertStableURL string `json:"certStableUrl"`
PrivateKey []byte `json:"-"`
Certificate []byte `json:"-"`
IssuerCertificate []byte `json:"-"`
CSR []byte `json:"-"`
}
// ObtainRequest The request to obtain certificate.
//
// The first domain in domains is used for the CommonName field of the certificate,
// all other domains are added using the Subject Alternate Names extension.
//
// A new private key is generated for every invocation of the function Obtain.
// If you do not want that you can supply your own private key in the privateKey parameter.
// If this parameter is non-nil it will be used instead of generating a new one.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
type ObtainRequest struct {
Domains []string
Bundle bool
PrivateKey crypto.PrivateKey
MustStaple bool
}
type resolver interface {
Solve(authorizations []acme.Authorization) error
}
type CertifierOptions struct {
KeyType certcrypto.KeyType
Timeout time.Duration
}
// Certifier A service to obtain/renew/revoke certificates.
type Certifier struct {
core *api.Core
resolver resolver
options CertifierOptions
}
// NewCertifier creates a Certifier.
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
return &Certifier{
core: core,
resolver: resolver,
options: options,
}
}
// Obtain tries to obtain a single certificate using all domains passed into it.
//
// This function will never return a partial certificate.
// If one domain in the list fails, the whole certificate will fail.
func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
if len(request.Domains) == 0 {
return nil, errors.New("no domains to obtain a certificate for")
}
domains := sanitizeDomain(request.Domains)
if request.Bundle {
log.Infof("[%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
} else {
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
}
order, err := c.core.Orders.New(domains)
if err != nil {
return nil, err
}
authz, err := c.getAuthorizations(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
err = c.resolver.Solve(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(obtainError)
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple)
if err != nil {
for _, auth := range authz {
failures[challenge.GetTargetedDomain(auth)] = err
}
}
// Do not return an empty failures map, because
// it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
//
// The domains are inferred from the CommonName and SubjectAltNames, if any.
// The private key for this CSR is not required.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
//
// This function will never return a partial certificate.
// If one domain in the list fails, the whole certificate will fail.
func (c *Certifier) ObtainForCSR(csr x509.CertificateRequest, bundle bool) (*Resource, error) {
// figure out what domains it concerns
// start with the common name
domains := certcrypto.ExtractDomainsCSR(&csr)
if bundle {
log.Infof("[%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
} else {
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
}
order, err := c.core.Orders.New(domains)
if err != nil {
return nil, err
}
authz, err := c.getAuthorizations(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
err = c.resolver.Solve(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(obtainError)
cert, err := c.getForCSR(domains, order, bundle, csr.Raw, nil)
if err != nil {
for _, auth := range authz {
failures[challenge.GetTargetedDomain(auth)] = err
}
}
if cert != nil {
// Add the CSR to the certificate so that it can be used for renewals.
cert.CSR = certcrypto.PEMEncode(&csr)
}
// Do not return an empty failures map,
// because it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool) (*Resource, error) {
if privateKey == nil {
var err error
privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType)
if err != nil {
return nil, err
}
}
// Determine certificate name(s) based on the authorization resources
commonName := domains[0]
// ACME draft Section 7.4 "Applying for Certificate Issuance"
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
// says:
// Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order
// object.
san := []string{commonName}
for _, auth := range order.Identifiers {
if auth.Value != commonName {
san = append(san, auth.Value)
}
}
// TODO: should the CSR be customizable?
csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple)
if err != nil {
return nil, err
}
return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey))
}
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr []byte, privateKeyPem []byte) (*Resource, error) {
respOrder, err := c.core.Orders.UpdateForCSR(order.Finalize, csr)
if err != nil {
return nil, err
}
commonName := domains[0]
certRes := &Resource{
Domain: commonName,
CertURL: respOrder.Certificate,
PrivateKey: privateKeyPem,
}
if respOrder.Status == acme.StatusValid {
// if the certificate is available right away, short cut!
ok, errR := c.checkResponse(respOrder, certRes, bundle)
if errR != nil {
return nil, errR
}
if ok {
return certRes, nil
}
}
timeout := c.options.Timeout
if c.options.Timeout <= 0 {
timeout = 30 * time.Second
}
err = wait.For("certificate", timeout, timeout/60, func() (bool, error) {
ord, errW := c.core.Orders.Get(order.Location)
if errW != nil {
return false, errW
}
done, errW := c.checkResponse(ord, certRes, bundle)
if errW != nil {
return false, errW
}
return done, nil
})
return certRes, err
}
// checkResponse checks to see if the certificate is ready and a link is contained in the response.
//
// If so, loads it into certRes and returns true.
// If the cert is not yet ready, it returns false.
//
// The certRes input should already have the Domain (common name) field populated.
//
// If bundle is true, the certificate will be bundled with the issuer's cert.
func (c *Certifier) checkResponse(order acme.Order, certRes *Resource, bundle bool) (bool, error) {
valid, err := checkOrderStatus(order)
if err != nil || !valid {
return valid, err
}
cert, issuer, err := c.core.Certificates.Get(order.Certificate, bundle)
if err != nil {
return false, err
}
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
certRes.IssuerCertificate = issuer
certRes.Certificate = cert
certRes.CertURL = order.Certificate
certRes.CertStableURL = order.Certificate
return true, nil
}
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
func (c *Certifier) Revoke(cert []byte) error {
certificates, err := certcrypto.ParsePEMBundle(cert)
if err != nil {
return err
}
x509Cert := certificates[0]
if x509Cert.IsCA {
return fmt.Errorf("certificate bundle starts with a CA certificate")
}
revokeMsg := acme.RevokeCertMessage{
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
}
return c.core.Certificates.Revoke(revokeMsg)
}
// Renew takes a Resource and tries to renew the certificate.
//
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
// Please be aware that this function will return a new certificate in ANY case that is not an error.
// If the server does not provide us with a new cert on a GET request to the CertURL
// this function will start a new-cert flow where a new certificate gets generated.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
//
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool) (*Resource, error) {
// Input certificate is PEM encoded.
// Decode it here as we may need the decoded cert later on in the renewal process.
// The input may be a bundle or a single certificate.
certificates, err := certcrypto.ParsePEMBundle(certRes.Certificate)
if err != nil {
return nil, err
}
x509Cert := certificates[0]
if x509Cert.IsCA {
return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", certRes.Domain)
}
// This is just meant to be informal for the user.
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
log.Infof("[%s] acme: Trying renewal with %d hours remaining", certRes.Domain, int(timeLeft.Hours()))
// We always need to request a new certificate to renew.
// Start by checking to see if the certificate was based off a CSR,
// and use that if it's defined.
if len(certRes.CSR) > 0 {
csr, errP := certcrypto.PemDecodeTox509CSR(certRes.CSR)
if errP != nil {
return nil, errP
}
return c.ObtainForCSR(*csr, bundle)
}
var privateKey crypto.PrivateKey
if certRes.PrivateKey != nil {
privateKey, err = certcrypto.ParsePEMPrivateKey(certRes.PrivateKey)
if err != nil {
return nil, err
}
}
query := ObtainRequest{
Domains: certcrypto.ExtractDomains(x509Cert),
Bundle: bundle,
PrivateKey: privateKey,
MustStaple: mustStaple,
}
return c.Obtain(query)
}
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
// the parsed response, and an error, if any.
//
// The returned []byte can be passed directly into the OCSPStaple property of a tls.Certificate.
// If the bundle only contains the issued certificate,
// this function will try to get the issuer certificate from the IssuingCertificateURL in the certificate.
//
// If the []byte and/or ocsp.Response return values are nil, the OCSP status may be assumed OCSPUnknown.
func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
certificates, err := certcrypto.ParsePEMBundle(bundle)
if err != nil {
return nil, nil, err
}
// We expect the certificate slice to be ordered downwards the chain.
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
// which should always be the first two certificates.
// If there's no OCSP server listed in the leaf cert, there's nothing to do.
// And if we have only one certificate so far, we need to get the issuer cert.
issuedCert := certificates[0]
if len(issuedCert.OCSPServer) == 0 {
return nil, nil, errors.New("no OCSP server specified in cert")
}
if len(certificates) == 1 {
// TODO: build fallback. If this fails, check the remaining array entries.
if len(issuedCert.IssuingCertificateURL) == 0 {
return nil, nil, errors.New("no issuing certificate URL")
}
resp, errC := c.core.HTTPClient.Get(issuedCert.IssuingCertificateURL[0])
if errC != nil {
return nil, nil, errC
}
defer resp.Body.Close()
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if errC != nil {
return nil, nil, errC
}
issuerCert, errC := x509.ParseCertificate(issuerBytes)
if errC != nil {
return nil, nil, errC
}
// Insert it into the slice on position 0
// We want it ordered right SRV CRT -> CA
certificates = append(certificates, issuerCert)
}
issuerCert := certificates[1]
// Finally kick off the OCSP request.
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
if err != nil {
return nil, nil, err
}
resp, err := c.core.HTTPClient.Post(issuedCert.OCSPServer[0], "application/ocsp-request", bytes.NewReader(ocspReq))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if err != nil {
return nil, nil, err
}
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
if err != nil {
return nil, nil, err
}
return ocspResBytes, ocspRes, nil
}
// Get attempts to fetch the certificate at the supplied URL.
// The URL is the same as what would normally be supplied at the Resource's CertURL.
//
// The returned Resource will not have the PrivateKey and CSR fields populated as these will not be available.
//
// If bundle is true, the Certificate field in the returned Resource includes the issuer certificate.
func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
cert, issuer, err := c.core.Certificates.Get(url, bundle)
if err != nil {
return nil, err
}
// Parse the returned cert bundle so that we can grab the domain from the common name.
x509Certs, err := certcrypto.ParsePEMBundle(cert)
if err != nil {
return nil, err
}
return &Resource{
Domain: x509Certs[0].Subject.CommonName,
Certificate: cert,
IssuerCertificate: issuer,
CertURL: url,
CertStableURL: url,
}, nil
}
func checkOrderStatus(order acme.Order) (bool, error) {
switch order.Status {
case acme.StatusValid:
return true, nil
case acme.StatusInvalid:
return false, order.Error
default:
return false, nil
}
}
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
// The domain name MUST be encoded
// in the form in which it would appear in a certificate. That is, it
// MUST be encoded according to the rules in Section 7 of [RFC5280].
//
// https://tools.ietf.org/html/rfc5280#section-7
func sanitizeDomain(domains []string) []string {
var sanitizedDomains []string
for _, domain := range domains {
sanitizedDomain, err := idna.ToASCII(domain)
if err != nil {
log.Infof("skip domain %q: unable to sanitize (punnycode): %v", domain, err)
} else {
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
}
}
return sanitizedDomains
}

30
vendor/github.com/go-acme/lego/certificate/errors.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package certificate
import (
"bytes"
"fmt"
"sort"
)
// obtainError is returned when there are specific errors available per domain.
type obtainError map[string]error
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
var domains []string
for domain := range e {
domains = append(domains, domain)
}
sort.Strings(domains)
for _, domain := range domains {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
}
return buffer.String()
}
type domainError struct {
Domain string
Error error
}

44
vendor/github.com/go-acme/lego/challenge/challenges.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
package challenge
import (
"fmt"
"github.com/go-acme/lego/acme"
)
// Type is a string that identifies a particular challenge type and version of ACME challenge.
type Type string
const (
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
// Note: ChallengePath returns the URL path to fulfill this challenge
HTTP01 = Type("http-01")
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
// Note: GetRecord returns a DNS record which will fulfill this challenge
DNS01 = Type("dns-01")
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
TLSALPN01 = Type("tls-alpn-01")
)
func (t Type) String() string {
return string(t)
}
func FindChallenge(chlgType Type, authz acme.Authorization) (acme.Challenge, error) {
for _, chlg := range authz.Challenges {
if chlg.Type == string(chlgType) {
return chlg, nil
}
}
return acme.Challenge{}, fmt.Errorf("[%s] acme: unable to find challenge %s", GetTargetedDomain(authz), chlgType)
}
func GetTargetedDomain(authz acme.Authorization) string {
if authz.Wildcard {
return "*." + authz.Identifier.Value
}
return authz.Identifier.Value
}

View File

@ -0,0 +1,16 @@
package dns01
import "github.com/miekg/dns"
// Update FQDN with CNAME if any
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
for _, rr := range r.Answer {
if cn, ok := rr.(*dns.CNAME); ok {
if cn.Hdr.Name == fqdn {
return cn.Target
}
}
}
return fqdn
}

View File

@ -0,0 +1,188 @@
package dns01
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
"strconv"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/platform/wait"
"github.com/miekg/dns"
)
const (
// DefaultPropagationTimeout default propagation timeout
DefaultPropagationTimeout = 60 * time.Second
// DefaultPollingInterval default polling interval
DefaultPollingInterval = 2 * time.Second
// DefaultTTL default TTL
DefaultTTL = 120
)
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
type ChallengeOption func(*Challenge) error
// CondOption Conditional challenge option.
func CondOption(condition bool, opt ChallengeOption) ChallengeOption {
if !condition {
// NoOp options
return func(*Challenge) error {
return nil
}
}
return opt
}
// Challenge implements the dns-01 challenge
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
preCheck preCheck
dnsTimeout time.Duration
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
chlg := &Challenge{
core: core,
validate: validate,
provider: provider,
preCheck: newPreCheck(),
dnsTimeout: 10 * time.Second,
}
for _, opt := range opts {
err := opt(chlg)
if err != nil {
log.Infof("challenge option error: %v", err)
}
}
return chlg
}
// PreSolve just submits the txt record to the dns provider.
// It does not validate record propagation, or do anything at all with the acme server.
func (c *Challenge) PreSolve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Preparing to solve DNS-01", domain)
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil {
return err
}
if c.provider == nil {
return fmt.Errorf("[%s] acme: no DNS Provider configured", domain)
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %s", domain, err)
}
return nil
}
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Trying to solve DNS-01", domain)
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil {
return err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
fqdn, value := GetRecord(authz.Identifier.Value, keyAuth)
var timeout, interval time.Duration
switch provider := c.provider.(type) {
case challenge.ProviderTimeout:
timeout, interval = provider.Timeout()
default:
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
}
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
err = wait.For("propagation", timeout, interval, func() (bool, error) {
stop, errP := c.preCheck.call(domain, fqdn, value)
if !stop || errP != nil {
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
}
return stop, errP
})
if err != nil {
return err
}
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}
// CleanUp cleans the challenge.
func (c *Challenge) CleanUp(authz acme.Authorization) error {
log.Infof("[%s] acme: Cleaning DNS-01 challenge", challenge.GetTargetedDomain(authz))
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil {
return err
}
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
return c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
}
func (c *Challenge) Sequential() (bool, time.Duration) {
if p, ok := c.provider.(sequential); ok {
return ok, p.Sequential()
}
return false, 0
}
type sequential interface {
Sequential() time.Duration
}
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge
func GetRecord(domain, keyAuth string) (fqdn string, value string) {
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
// base64URL encoding without padding
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
// Check if the domain has CNAME then return that
if err == nil && r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
}
}
return
}

View File

@ -0,0 +1,59 @@
package dns01
import (
"bufio"
"fmt"
"os"
"time"
)
const (
dnsTemplate = `%s %d IN TXT "%s"`
)
// DNSProviderManual is an implementation of the ChallengeProvider interface
type DNSProviderManual struct{}
// NewDNSProviderManual returns a DNSProviderManual instance.
func NewDNSProviderManual() (*DNSProviderManual, error) {
return &DNSProviderManual{}, nil
}
// Present prints instructions for manually creating the TXT record
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
fqdn, value := GetRecord(domain, keyAuth)
authZone, err := FindZoneByFqdn(fqdn)
if err != nil {
return err
}
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, value)
fmt.Printf("lego: Press 'Enter' when you are done\n")
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
return err
}
// CleanUp prints instructions for manually removing the TXT record
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := GetRecord(domain, keyAuth)
authZone, err := FindZoneByFqdn(fqdn)
if err != nil {
return err
}
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, "...")
return nil
}
// Sequential All DNS challenges for this provider will be resolved sequentially.
// Returns the interval between each iteration.
func (d *DNSProviderManual) Sequential() time.Duration {
return DefaultPropagationTimeout
}

19
vendor/github.com/go-acme/lego/challenge/dns01/fqdn.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
package dns01
// ToFqdn converts the name into a fqdn appending a trailing dot.
func ToFqdn(name string) string {
n := len(name)
if n == 0 || name[n-1] == '.' {
return name
}
return name + "."
}
// UnFqdn converts the fqdn into a name removing the trailing dot.
func UnFqdn(name string) string {
n := len(name)
if n != 0 && name[n-1] == '.' {
return name[:n-1]
}
return name
}

View File

@ -0,0 +1,232 @@
package dns01
import (
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/miekg/dns"
)
const defaultResolvConf = "/etc/resolv.conf"
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
var dnsTimeout = 10 * time.Second
var (
fqdnToZone = map[string]string{}
muFqdnToZone sync.Mutex
)
var defaultNameservers = []string{
"google-public-dns-a.google.com:53",
"google-public-dns-b.google.com:53",
}
// recursiveNameservers are used to pre-check DNS propagation
var recursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
func ClearFqdnCache() {
muFqdnToZone.Lock()
fqdnToZone = map[string]string{}
muFqdnToZone.Unlock()
}
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
return func(_ *Challenge) error {
dnsTimeout = timeout
return nil
}
}
func AddRecursiveNameservers(nameservers []string) ChallengeOption {
return func(_ *Challenge) error {
recursiveNameservers = ParseNameservers(nameservers)
return nil
}
}
// getNameservers attempts to get systems nameservers before falling back to the defaults
func getNameservers(path string, defaults []string) []string {
config, err := dns.ClientConfigFromFile(path)
if err != nil || len(config.Servers) == 0 {
return defaults
}
return ParseNameservers(config.Servers)
}
func ParseNameservers(servers []string) []string {
var resolvers []string
for _, resolver := range servers {
// ensure all servers have a port number
if _, _, err := net.SplitHostPort(resolver); err != nil {
resolvers = append(resolvers, net.JoinHostPort(resolver, "53"))
} else {
resolvers = append(resolvers, resolver)
}
}
return resolvers
}
// lookupNameservers returns the authoritative nameservers for the given fqdn.
func lookupNameservers(fqdn string) ([]string, error) {
var authoritativeNss []string
zone, err := FindZoneByFqdn(fqdn)
if err != nil {
return nil, fmt.Errorf("could not determine the zone: %v", err)
}
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
if err != nil {
return nil, err
}
for _, rr := range r.Answer {
if ns, ok := rr.(*dns.NS); ok {
authoritativeNss = append(authoritativeNss, strings.ToLower(ns.Ns))
}
}
if len(authoritativeNss) > 0 {
return authoritativeNss, nil
}
return nil, fmt.Errorf("could not determine authoritative nameservers")
}
// FindZoneByFqdn determines the zone apex for the given fqdn
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
func FindZoneByFqdn(fqdn string) (string, error) {
return FindZoneByFqdnCustom(fqdn, recursiveNameservers)
}
// FindZoneByFqdnCustom determines the zone apex for the given fqdn
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
muFqdnToZone.Lock()
defer muFqdnToZone.Unlock()
// Do we have it cached?
if zone, ok := fqdnToZone[fqdn]; ok {
return zone, nil
}
var err error
var in *dns.Msg
labelIndexes := dns.Split(fqdn)
for _, index := range labelIndexes {
domain := fqdn[index:]
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
if err != nil {
continue
}
if in == nil {
continue
}
switch in.Rcode {
case dns.RcodeSuccess:
// Check if we got a SOA RR in the answer section
if len(in.Answer) == 0 {
continue
}
// CNAME records cannot/should not exist at the root of a zone.
// So we skip a domain when a CNAME is found.
if dnsMsgContainsCNAME(in) {
continue
}
for _, ans := range in.Answer {
if soa, ok := ans.(*dns.SOA); ok {
zone := soa.Hdr.Name
fqdnToZone[fqdn] = zone
return zone, nil
}
}
case dns.RcodeNameError:
// NXDOMAIN
default:
// Any response code other than NOERROR and NXDOMAIN is treated as error
return "", fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
}
}
return "", fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
}
// dnsMsgContainsCNAME checks for a CNAME answer in msg
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
for _, ans := range msg.Answer {
if _, ok := ans.(*dns.CNAME); ok {
return true
}
}
return false
}
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
m := createDNSMsg(fqdn, rtype, recursive)
var in *dns.Msg
var err error
for _, ns := range nameservers {
in, err = sendDNSQuery(m, ns)
if err == nil && len(in.Answer) > 0 {
break
}
}
return in, err
}
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
m := new(dns.Msg)
m.SetQuestion(fqdn, rtype)
m.SetEdns0(4096, false)
if !recursive {
m.RecursionDesired = false
}
return m
}
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
in, _, err := udp.Exchange(m, ns)
if in != nil && in.Truncated {
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
// If the TCP request succeeds, the err will reset to nil
in, _, err = tcp.Exchange(m, ns)
}
return in, err
}
func formatDNSError(msg *dns.Msg, err error) string {
var parts []string
if msg != nil {
parts = append(parts, dns.RcodeToString[msg.Rcode])
}
if err != nil {
parts = append(parts, fmt.Sprintf("%v", err))
}
if len(parts) > 0 {
return ": " + strings.Join(parts, " ")
}
return ""
}

View File

@ -0,0 +1,127 @@
package dns01
import (
"errors"
"fmt"
"net"
"strings"
"github.com/miekg/dns"
)
// PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready.
type PreCheckFunc func(fqdn, value string) (bool, error)
// WrapPreCheckFunc wraps a PreCheckFunc in order to do extra operations before or after
// the main check, put it in a loop, etc.
type WrapPreCheckFunc func(domain, fqdn, value string, check PreCheckFunc) (bool, error)
// WrapPreCheck Allow to define checks before notifying ACME that the DNS challenge is ready.
func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.checkFunc = wrap
return nil
}
}
// AddPreCheck Allow to define checks before notifying ACME that the DNS challenge is ready.
// Deprecated: use WrapPreCheck instead.
func AddPreCheck(preCheck PreCheckFunc) ChallengeOption {
// Prevent race condition
check := preCheck
return func(chlg *Challenge) error {
chlg.preCheck.checkFunc = func(_, fqdn, value string, _ PreCheckFunc) (bool, error) {
if check == nil {
return false, errors.New("invalid preCheck: preCheck is nil")
}
return check(fqdn, value)
}
return nil
}
}
func DisableCompletePropagationRequirement() ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.requireCompletePropagation = false
return nil
}
}
type preCheck struct {
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
checkFunc WrapPreCheckFunc
// require the TXT record to be propagated to all authoritative name servers
requireCompletePropagation bool
}
func newPreCheck() preCheck {
return preCheck{
requireCompletePropagation: true,
}
}
func (p preCheck) call(domain, fqdn, value string) (bool, error) {
if p.checkFunc == nil {
return p.checkDNSPropagation(fqdn, value)
}
return p.checkFunc(domain, fqdn, value, p.checkDNSPropagation)
}
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
// Initial attempt to resolve at the recursive NS
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
if err != nil {
return false, err
}
if !p.requireCompletePropagation {
return true, nil
}
if r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
}
authoritativeNss, err := lookupNameservers(fqdn)
if err != nil {
return false, err
}
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
}
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
for _, ns := range nameservers {
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
if err != nil {
return false, err
}
if r.Rcode != dns.RcodeSuccess {
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
}
var records []string
var found bool
for _, rr := range r.Answer {
if txt, ok := rr.(*dns.TXT); ok {
record := strings.Join(txt.Txt, "")
records = append(records, record)
if record == value {
found = true
break
}
}
}
if !found {
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s, value: %s]: %s", ns, fqdn, value, strings.Join(records, " ,"))
}
}
return true, nil
}

View File

@ -0,0 +1,65 @@
package http01
import (
"fmt"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
)
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
// ChallengePath returns the URL path for the `http-01` challenge
func ChallengePath(token string) string {
return "/.well-known/acme-challenge/" + token
}
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
return &Challenge{
core: core,
validate: validate,
provider: provider,
}
}
func (c *Challenge) SetProvider(provider challenge.Provider) {
c.provider = provider
}
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
chlng, err := challenge.FindChallenge(challenge.HTTP01, authz)
if err != nil {
return err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %v", domain, err)
}
defer func() {
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v", domain, err)
}
}()
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}

View File

@ -0,0 +1,96 @@
package http01
import (
"fmt"
"net"
"net/http"
"strings"
"github.com/go-acme/lego/log"
)
// ProviderServer implements ChallengeProvider for `http-01` challenge
// It may be instantiated without using the NewProviderServer function if
// you want only to use the default values.
type ProviderServer struct {
iface string
port string
done chan bool
listener net.Listener
}
// NewProviderServer creates a new ProviderServer on the selected interface and port.
// Setting iface and / or port to an empty string will make the server fall back to
// the "any" interface and port 80 respectively.
func NewProviderServer(iface, port string) *ProviderServer {
return &ProviderServer{iface: iface, port: port}
}
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
if s.port == "" {
s.port = "80"
}
var err error
s.listener, err = net.Listen("tcp", s.GetAddress())
if err != nil {
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
}
s.done = make(chan bool)
go s.serve(domain, token, keyAuth)
return nil
}
func (s *ProviderServer) GetAddress() string {
return net.JoinHostPort(s.iface, s.port)
}
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
if s.listener == nil {
return nil
}
s.listener.Close()
<-s.done
return nil
}
func (s *ProviderServer) serve(domain, token, keyAuth string) {
path := ChallengePath(token)
// The handler validates the HOST header and request type.
// For validation it then writes the token the server returned with the challenge
mux := http.NewServeMux()
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.Host, domain) && r.Method == http.MethodGet {
w.Header().Add("Content-Type", "text/plain")
_, err := w.Write([]byte(keyAuth))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Infof("[%s] Served key authentication", domain)
} else {
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method)
_, err := w.Write([]byte("TEST"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
})
httpServer := &http.Server{Handler: mux}
// Once httpServer is shut down
// we don't want any lingering connections, so disable KeepAlives.
httpServer.SetKeepAlivesEnabled(false)
err := httpServer.Serve(s.listener)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
log.Println(err)
}
s.done <- true
}

View File

@ -1,28 +1,28 @@
package acme
package challenge
import "time"
// ChallengeProvider enables implementing a custom challenge
// Provider enables implementing a custom challenge
// provider. Present presents the solution to a challenge available to
// be solved. CleanUp will be called by the challenge if Present ends
// in a non-error state.
type ChallengeProvider interface {
type Provider interface {
Present(domain, token, keyAuth string) error
CleanUp(domain, token, keyAuth string) error
}
// ChallengeProviderTimeout allows for implementing a
// ChallengeProvider where an unusually long timeout is required when
// ProviderTimeout allows for implementing a
// Provider where an unusually long timeout is required when
// waiting for an ACME challenge to be satisfied, such as when
// checking for DNS record progagation. If an implementor of a
// ChallengeProvider provides a Timeout method, then the return values
// checking for DNS record propagation. If an implementor of a
// Provider provides a Timeout method, then the return values
// of the Timeout method will be used when appropriate by the acme
// package. The interval value is the time between checks.
//
// The default values used for timeout and interval are 60 seconds and
// 2 seconds respectively. These are used when no Timeout method is
// defined for the ChallengeProvider.
type ChallengeProviderTimeout interface {
ChallengeProvider
// defined for the Provider.
type ProviderTimeout interface {
Provider
Timeout() (timeout, interval time.Duration)
}

View File

@ -0,0 +1,25 @@
package resolver
import (
"bytes"
"fmt"
"sort"
)
// obtainError is returned when there are specific errors available per domain.
type obtainError map[string]error
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
var domains []string
for domain := range e {
domains = append(domains, domain)
}
sort.Strings(domains)
for _, domain := range domains {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
}
return buffer.String()
}

View File

@ -0,0 +1,173 @@
package resolver
import (
"fmt"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
)
// Interface for all challenge solvers to implement.
type solver interface {
Solve(authorization acme.Authorization) error
}
// Interface for challenges like dns, where we can set a record in advance for ALL challenges.
// This saves quite a bit of time vs creating the records and solving them serially.
type preSolver interface {
PreSolve(authorization acme.Authorization) error
}
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
type cleanup interface {
CleanUp(authorization acme.Authorization) error
}
type sequential interface {
Sequential() (bool, time.Duration)
}
// an authz with the solver we have chosen and the index of the challenge associated with it
type selectedAuthSolver struct {
authz acme.Authorization
solver solver
}
type Prober struct {
solverManager *SolverManager
}
func NewProber(solverManager *SolverManager) *Prober {
return &Prober{
solverManager: solverManager,
}
}
// Solve Looks through the challenge combinations to find a solvable match.
// Then solves the challenges in series and returns.
func (p *Prober) Solve(authorizations []acme.Authorization) error {
failures := make(obtainError)
var authSolvers []*selectedAuthSolver
var authSolversSequential []*selectedAuthSolver
// Loop through the resources, basically through the domains.
// First pass just selects a solver for each authz.
for _, authz := range authorizations {
domain := challenge.GetTargetedDomain(authz)
if authz.Status == acme.StatusValid {
// Boulder might recycle recent validated authz (see issue #267)
log.Infof("[%s] acme: authorization already valid; skipping challenge", domain)
continue
}
if solvr := p.solverManager.chooseSolver(authz); solvr != nil {
authSolver := &selectedAuthSolver{authz: authz, solver: solvr}
switch s := solvr.(type) {
case sequential:
if ok, _ := s.Sequential(); ok {
authSolversSequential = append(authSolversSequential, authSolver)
} else {
authSolvers = append(authSolvers, authSolver)
}
default:
authSolvers = append(authSolvers, authSolver)
}
} else {
failures[domain] = fmt.Errorf("[%s] acme: could not determine solvers", domain)
}
}
parallelSolve(authSolvers, failures)
sequentialSolve(authSolversSequential, failures)
// Be careful not to return an empty failures map,
// for even an empty obtainError is a non-nil error value
if len(failures) > 0 {
return failures
}
return nil
}
func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
for i, authSolver := range authSolvers {
// Submit the challenge
domain := challenge.GetTargetedDomain(authSolver.authz)
if solvr, ok := authSolver.solver.(preSolver); ok {
err := solvr.PreSolve(authSolver.authz)
if err != nil {
failures[domain] = err
cleanUp(authSolver.solver, authSolver.authz)
continue
}
}
// Solve challenge
err := authSolver.solver.Solve(authSolver.authz)
if err != nil {
failures[domain] = err
cleanUp(authSolver.solver, authSolver.authz)
continue
}
// Clean challenge
cleanUp(authSolver.solver, authSolver.authz)
if len(authSolvers)-1 > i {
solvr := authSolver.solver.(sequential)
_, interval := solvr.Sequential()
log.Infof("sequence: wait for %s", interval)
time.Sleep(interval)
}
}
}
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
// For all valid preSolvers, first submit the challenges so they have max time to propagate
for _, authSolver := range authSolvers {
authz := authSolver.authz
if solvr, ok := authSolver.solver.(preSolver); ok {
err := solvr.PreSolve(authz)
if err != nil {
failures[challenge.GetTargetedDomain(authz)] = err
}
}
}
defer func() {
// Clean all created TXT records
for _, authSolver := range authSolvers {
cleanUp(authSolver.solver, authSolver.authz)
}
}()
// Finally solve all challenges for real
for _, authSolver := range authSolvers {
authz := authSolver.authz
domain := challenge.GetTargetedDomain(authz)
if failures[domain] != nil {
// already failed in previous loop
continue
}
err := authSolver.solver.Solve(authz)
if err != nil {
failures[domain] = err
}
}
}
func cleanUp(solvr solver, authz acme.Authorization) {
if solvr, ok := solvr.(cleanup); ok {
domain := challenge.GetTargetedDomain(authz)
err := solvr.CleanUp(authz)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v ", domain, err)
}
}
}

View File

@ -0,0 +1,169 @@
package resolver
import (
"context"
"errors"
"fmt"
"sort"
"strconv"
"time"
"github.com/cenkalti/backoff"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/challenge/http01"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/log"
)
type byType []acme.Challenge
func (a byType) Len() int { return len(a) }
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byType) Less(i, j int) bool { return a[i].Type > a[j].Type }
type SolverManager struct {
core *api.Core
solvers map[challenge.Type]solver
}
func NewSolversManager(core *api.Core) *SolverManager {
return &SolverManager{
solvers: map[challenge.Type]solver{},
core: core,
}
}
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
return nil
}
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error {
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p)
return nil
}
// SetDNS01Provider specifies a custom provider p that can solve the given DNS-01 challenge.
func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.ChallengeOption) error {
c.solvers[challenge.DNS01] = dns01.NewChallenge(c.core, validate, p, opts...)
return nil
}
// Remove Remove a challenge type from the available solvers.
func (c *SolverManager) Remove(chlgType challenge.Type) {
delete(c.solvers, chlgType)
}
// Checks all challenges from the server in order and returns the first matching solver.
func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
// Allow to have a deterministic challenge order
sort.Sort(byType(authz.Challenges))
domain := challenge.GetTargetedDomain(authz)
for _, chlg := range authz.Challenges {
if solvr, ok := c.solvers[challenge.Type(chlg.Type)]; ok {
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
return solvr
}
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
}
return nil
}
func validate(core *api.Core, domain string, chlg acme.Challenge) error {
chlng, err := core.Challenges.New(chlg.URL)
if err != nil {
return fmt.Errorf("failed to initiate challenge: %v", err)
}
valid, err := checkChallengeStatus(chlng)
if err != nil {
return err
}
if valid {
log.Infof("[%s] The server validated our request", domain)
return nil
}
ra, err := strconv.Atoi(chlng.RetryAfter)
if err != nil {
// The ACME server MUST return a Retry-After.
// If it doesn't, we'll just poll hard.
// Boulder does not implement the ability to retry challenges or the Retry-After header.
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
ra = 5
}
initialInterval := time.Duration(ra) * time.Second
bo := backoff.NewExponentialBackOff()
bo.InitialInterval = initialInterval
bo.MaxInterval = 10 * initialInterval
bo.MaxElapsedTime = 100 * initialInterval
ctx, cancel := context.WithCancel(context.Background())
// After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request.
operation := func() error {
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
if err != nil {
cancel()
return err
}
valid, err := checkAuthorizationStatus(authz)
if err != nil {
cancel()
return err
}
if valid {
log.Infof("[%s] The server validated our request", domain)
return nil
}
return errors.New("the server didn't respond to our request")
}
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
}
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
switch chlng.Status {
case acme.StatusValid:
return true, nil
case acme.StatusPending, acme.StatusProcessing:
return false, nil
case acme.StatusInvalid:
return false, chlng.Error
default:
return false, errors.New("the server returned an unexpected state")
}
}
func checkAuthorizationStatus(authz acme.Authorization) (bool, error) {
switch authz.Status {
case acme.StatusValid:
return true, nil
case acme.StatusPending, acme.StatusProcessing:
return false, nil
case acme.StatusDeactivated, acme.StatusExpired, acme.StatusRevoked:
return false, fmt.Errorf("the authorization state %s", authz.Status)
case acme.StatusInvalid:
for _, chlg := range authz.Challenges {
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
return false, chlg.Error
}
}
return false, fmt.Errorf("the authorization state %s", authz.Status)
default:
return false, errors.New("the server returned an unexpected state")
}
}

View File

@ -0,0 +1,129 @@
package tlsalpn01
import (
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
)
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
return &Challenge{
core: core,
validate: validate,
provider: provider,
}
}
func (c *Challenge) SetProvider(provider challenge.Provider) {
c.provider = provider
}
// Solve manages the provider to validate and solve the challenge.
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := authz.Identifier.Value
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
if err != nil {
return err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
err = c.provider.Present(domain, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %v", challenge.GetTargetedDomain(authz), err)
}
defer func() {
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v", challenge.GetTargetedDomain(authz), err)
}
}()
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}
// ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
// and domain name for the `tls-alpn-01` challenge.
func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
// Compute the SHA-256 digest of the key authorization.
zBytes := sha256.Sum256([]byte(keyAuth))
value, err := asn1.Marshal(zBytes[:sha256.Size])
if err != nil {
return nil, nil, err
}
// Add the keyAuth digest as the acmeValidation-v1 extension
// (marked as critical such that it won't be used by non-ACME software).
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
extensions := []pkix.Extension{
{
Id: idPeAcmeIdentifierV1,
Critical: true,
Value: value,
},
}
// Generate a new RSA key for the certificates.
tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
if err != nil {
return nil, nil, err
}
rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
if err != nil {
return nil, nil, err
}
// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
return tempCertPEM, rsaPrivatePEM, nil
}
// ChallengeCert returns a certificate with the acmeValidation-v1 extension
// and domain name for the `tls-alpn-01` challenge.
func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
if err != nil {
return nil, err
}
return &cert, nil
}

View File

@ -1,48 +1,54 @@
package acme
package tlsalpn01
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"github.com/go-acme/lego/log"
)
const (
// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
ACMETLS1Protocol = "acme-tls/1"
// defaultTLSPort is the port that the TLSALPNProviderServer will default to
// defaultTLSPort is the port that the ProviderServer will default to
// when no other port is provided.
defaultTLSPort = "443"
)
// TLSALPNProviderServer implements ChallengeProvider for `TLS-ALPN-01`
// challenge. It may be instantiated without using the NewTLSALPNProviderServer
// ProviderServer implements ChallengeProvider for `TLS-ALPN-01` challenge.
// It may be instantiated without using the NewProviderServer
// if you want only to use the default values.
type TLSALPNProviderServer struct {
type ProviderServer struct {
iface string
port string
listener net.Listener
}
// NewTLSALPNProviderServer creates a new TLSALPNProviderServer on the selected
// interface and port. Setting iface and / or port to an empty string will make
// the server fall back to the "any" interface and port 443 respectively.
func NewTLSALPNProviderServer(iface, port string) *TLSALPNProviderServer {
return &TLSALPNProviderServer{iface: iface, port: port}
// NewProviderServer creates a new ProviderServer on the selected interface and port.
// Setting iface and / or port to an empty string will make the server fall back to
// the "any" interface and port 443 respectively.
func NewProviderServer(iface, port string) *ProviderServer {
return &ProviderServer{iface: iface, port: port}
}
func (s *ProviderServer) GetAddress() string {
return net.JoinHostPort(s.iface, s.port)
}
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN
// spec.
func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
if t.port == "" {
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
if s.port == "" {
// Fallback to port 443 if the port was not provided.
t.port = defaultTLSPort
s.port = defaultTLSPort
}
// Generate the challenge certificate using the provided keyAuth and domain.
cert, err := TLSALPNChallengeCert(domain, keyAuth)
cert, err := ChallengeCert(domain, keyAuth)
if err != nil {
return err
}
@ -58,27 +64,30 @@ func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
tlsConf.NextProtos = []string{ACMETLS1Protocol}
// Create the listener with the created tls.Config.
t.listener, err = tls.Listen("tcp", net.JoinHostPort(t.iface, t.port), tlsConf)
s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
if err != nil {
return fmt.Errorf("could not start HTTPS server for challenge -> %v", err)
}
// Shut the server down when we're finished.
go func() {
http.Serve(t.listener, nil)
err := http.Serve(s.listener, nil)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
log.Println(err)
}
}()
return nil
}
// CleanUp closes the HTTPS server.
func (t *TLSALPNProviderServer) CleanUp(domain, token, keyAuth string) error {
if t.listener == nil {
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
if s.listener == nil {
return nil
}
// Server was created, close it.
if err := t.listener.Close(); err != nil && err != http.ErrServerClosed {
if err := s.listener.Close(); err != nil && err != http.ErrServerClosed {
return err
}

74
vendor/github.com/go-acme/lego/lego/client.go generated vendored Normal file
View File

@ -0,0 +1,74 @@
package lego
import (
"errors"
"net/url"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge/resolver"
"github.com/go-acme/lego/registration"
)
// Client is the user-friendly way to ACME
type Client struct {
Certificate *certificate.Certifier
Challenge *resolver.SolverManager
Registration *registration.Registrar
core *api.Core
}
// NewClient creates a new ACME client on behalf of the user.
// The client will depend on the ACME directory located at CADirURL for the rest of its actions.
// A private key of type keyType (see KeyType constants) will be generated when requesting a new certificate if one isn't provided.
func NewClient(config *Config) (*Client, error) {
if config == nil {
return nil, errors.New("a configuration must be provided")
}
_, err := url.Parse(config.CADirURL)
if err != nil {
return nil, err
}
if config.HTTPClient == nil {
return nil, errors.New("the HTTP client cannot be nil")
}
privateKey := config.User.GetPrivateKey()
if privateKey == nil {
return nil, errors.New("private key was nil")
}
var kid string
if reg := config.User.GetRegistration(); reg != nil {
kid = reg.URI
}
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, kid, privateKey)
if err != nil {
return nil, err
}
solversManager := resolver.NewSolversManager(core)
prober := resolver.NewProber(solversManager)
certifier := certificate.NewCertifier(core, prober, certificate.CertifierOptions{KeyType: config.Certificate.KeyType, Timeout: config.Certificate.Timeout})
return &Client{
Certificate: certifier,
Challenge: solversManager,
Registration: registration.NewRegistrar(core, config.User),
core: core,
}, nil
}
// GetToSURL returns the current ToS URL from the Directory
func (c *Client) GetToSURL() string {
return c.core.GetDirectory().Meta.TermsOfService
}
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
func (c *Client) GetExternalAccountRequired() bool {
return c.core.GetDirectory().Meta.ExternalAccountRequired
}

104
vendor/github.com/go-acme/lego/lego/client_config.go generated vendored Normal file
View File

@ -0,0 +1,104 @@
package lego
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"time"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/registration"
)
const (
// caCertificatesEnvVar is the environment variable name that can be used to
// specify the path to PEM encoded CA Certificates that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
// caServerNameEnvVar is the environment variable name that can be used to
// specify the CA server name that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
// LEDirectoryProduction URL to the Let's Encrypt production
LEDirectoryProduction = "https://acme-v02.api.letsencrypt.org/directory"
// LEDirectoryStaging URL to the Let's Encrypt staging
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
)
type Config struct {
CADirURL string
User registration.User
UserAgent string
HTTPClient *http.Client
Certificate CertificateConfig
}
func NewConfig(user registration.User) *Config {
return &Config{
CADirURL: LEDirectoryProduction,
User: user,
HTTPClient: createDefaultHTTPClient(),
Certificate: CertificateConfig{
KeyType: certcrypto.RSA2048,
Timeout: 30 * time.Second,
},
}
}
type CertificateConfig struct {
KeyType certcrypto.KeyType
Timeout time.Duration
}
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
// and potentially a custom *x509.CertPool
// based on the caCertificatesEnvVar environment variable (see the `initCertPool` function)
func createDefaultHTTPClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 15 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
ServerName: os.Getenv(caServerNameEnvVar),
RootCAs: initCertPool(),
},
},
}
}
// initCertPool creates a *x509.CertPool populated with the PEM certificates
// found in the filepath specified in the caCertificatesEnvVar OS environment
// variable. If the caCertificatesEnvVar is not set then initCertPool will
// return nil. If there is an error creating a *x509.CertPool from the provided
// caCertificatesEnvVar value then initCertPool will panic.
func initCertPool() *x509.CertPool {
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
customCAs, err := ioutil.ReadFile(customCACertsPath)
if err != nil {
panic(fmt.Sprintf("error reading %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
}
return certPool
}
return nil
}

View File

@ -6,7 +6,17 @@ import (
)
// Logger is an optional custom logger.
var Logger = log.New(os.Stdout, "", log.LstdFlags)
var Logger StdLogger = log.New(os.Stdout, "", log.LstdFlags)
// StdLogger interface for Standard Logger.
type StdLogger interface {
Fatal(args ...interface{})
Fatalln(args ...interface{})
Fatalf(format string, args ...interface{})
Print(args ...interface{})
Println(args ...interface{})
Printf(format string, args ...interface{})
}
// Fatal writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.

33
vendor/github.com/go-acme/lego/platform/wait/wait.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package wait
import (
"fmt"
"time"
"github.com/go-acme/lego/log"
)
// For polls the given function 'f', once every 'interval', up to 'timeout'.
func For(msg string, timeout, interval time.Duration, f func() (bool, error)) error {
log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
var lastErr string
timeUp := time.After(timeout)
for {
select {
case <-timeUp:
return fmt.Errorf("time limit exceeded: last error: %s", lastErr)
default:
}
stop, err := f()
if stop {
return nil
}
if err != nil {
lastErr = err.Error()
}
time.Sleep(interval)
}
}

146
vendor/github.com/go-acme/lego/registration/registar.go generated vendored Normal file
View File

@ -0,0 +1,146 @@
package registration
import (
"errors"
"net/http"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/log"
)
// Resource represents all important information about a registration
// of which the client needs to keep track itself.
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
type Resource struct {
Body acme.Account `json:"body,omitempty"`
URI string `json:"uri,omitempty"`
}
type RegisterOptions struct {
TermsOfServiceAgreed bool
}
type RegisterEABOptions struct {
TermsOfServiceAgreed bool
Kid string
HmacEncoded string
}
type Registrar struct {
core *api.Core
user User
}
func NewRegistrar(core *api.Core, user User) *Registrar {
return &Registrar{
core: core,
user: user,
}
}
// Register the current account to the ACME server.
func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
if r == nil || r.user == nil {
return nil, errors.New("acme: cannot register a nil client or user")
}
accMsg := acme.Account{
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
Contact: []string{},
}
if r.user.GetEmail() != "" {
log.Infof("acme: Registering account for %s", r.user.GetEmail())
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
}
account, err := r.core.Accounts.New(accMsg)
if err != nil {
// FIXME seems impossible
errorDetails, ok := err.(acme.ProblemDetails)
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
return nil, err
}
}
return &Resource{URI: account.Location, Body: account.Account}, nil
}
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOptions) (*Resource, error) {
accMsg := acme.Account{
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
Contact: []string{},
}
if r.user.GetEmail() != "" {
log.Infof("acme: Registering account for %s", r.user.GetEmail())
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
}
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
if err != nil {
errorDetails, ok := err.(acme.ProblemDetails)
// FIXME seems impossible
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
return nil, err
}
}
return &Resource{URI: account.Location, Body: account.Account}, nil
}
// QueryRegistration runs a POST request on the client's registration and returns the result.
//
// This is similar to the Register function,
// but acting on an existing registration link and resource.
func (r *Registrar) QueryRegistration() (*Resource, error) {
if r == nil || r.user == nil {
return nil, errors.New("acme: cannot query the registration of a nil client or user")
}
// Log the URL here instead of the email as the email may not be set
log.Infof("acme: Querying account for %s", r.user.GetRegistration().URI)
account, err := r.core.Accounts.Get(r.user.GetRegistration().URI)
if err != nil {
return nil, err
}
return &Resource{
Body: account,
// Location: header is not returned so this needs to be populated off of existing URI
URI: r.user.GetRegistration().URI,
}, nil
}
// DeleteRegistration deletes the client's user registration from the ACME server.
func (r *Registrar) DeleteRegistration() error {
if r == nil || r.user == nil {
return errors.New("acme: cannot unregister a nil client or user")
}
log.Infof("acme: Deleting account for %s", r.user.GetEmail())
return r.core.Accounts.Deactivate(r.user.GetRegistration().URI)
}
// ResolveAccountByKey will attempt to look up an account using the given account key
// and return its registration resource.
func (r *Registrar) ResolveAccountByKey() (*Resource, error) {
log.Infof("acme: Trying to resolve account by key")
accMsg := acme.Account{OnlyReturnExisting: true}
accountTransit, err := r.core.Accounts.New(accMsg)
if err != nil {
return nil, err
}
account, err := r.core.Accounts.Get(accountTransit.Location)
if err != nil {
return nil, err
}
return &Resource{URI: accountTransit.Location, Body: account}, nil
}

13
vendor/github.com/go-acme/lego/registration/user.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
package registration
import (
"crypto"
)
// User interface is to be implemented by users of this library.
// It is used by the client type to get user specific information.
type User interface {
GetEmail() string
GetRegistration() *Resource
GetPrivateKey() crypto.PrivateKey
}

View File

@ -1,7 +1,3 @@
**This package is currently in development and the API may not be stable.**
The API will become stable with v1.
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122)

1
vendor/github.com/google/uuid/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/google/uuid

View File

@ -15,8 +15,6 @@ func (uuid UUID) MarshalText() ([]byte, error) {
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
// See comment in ParseBytes why we do this.
// id, err := ParseBytes(data)
id, err := ParseBytes(data)
if err == nil {
*uuid = id

View File

@ -37,7 +37,6 @@ func SetNodeInterface(name string) bool {
}
func setNodeInterface(name string) bool {
iname, addr := getHardwareInterface(name) // null implementation for js
if iname != "" && addr != nil {
ifname = iname
@ -49,6 +48,7 @@ func setNodeInterface(name string) bool {
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
ifname = "random"
randomBits(nodeID[:])
return true
}

View File

@ -1,12 +0,0 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

View File

@ -1,4 +1,4 @@
// Copyright 2016 Google Inc. All rights reserved.
// Copyright 2018 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@ -35,20 +35,43 @@ const (
var rander = rand.Reader // random function
// Parse decodes s into a UUID or returns an error. Both the UUID form of
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
func Parse(s string) (UUID, error) {
var uuid UUID
if len(s) != 36 {
if len(s) != 36+9 {
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
}
switch len(s) {
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36:
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9:
if strings.ToLower(s[:9]) != "urn:uuid:" {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
case 36 + 2:
s = s[1:]
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
case 32:
var ok bool
for i := range uuid {
uuid[i], ok = xtob(s[i*2], s[i*2+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
@ -70,15 +93,29 @@ func Parse(s string) (UUID, error) {
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
var uuid UUID
if len(b) != 36 {
if len(b) != 36+9 {
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
}
switch len(b) {
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
}
b = b[9:]
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
b = b[1:]
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
var ok bool
for i := 0; i < 32; i += 2 {
uuid[i/2], ok = xtob(b[i], b[i+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
@ -97,6 +134,16 @@ func ParseBytes(b []byte) (UUID, error) {
return uuid, nil
}
// MustParse is like Parse but panics if the string cannot be parsed.
// It simplifies safe initialization of global variables holding compiled UUIDs.
func MustParse(s string) UUID {
uuid, err := Parse(s)
if err != nil {
panic(`uuid: Parse(` + s + `): ` + err.Error())
}
return uuid
}
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
// does not have a length of 16. The bytes are copied from the slice.
func FromBytes(b []byte) (uuid UUID, err error) {
@ -130,7 +177,7 @@ func (uuid UUID) URN() string {
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst[:], uuid[:4])
hex.Encode(dst, uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'

View File

@ -27,8 +27,12 @@ func New() UUID {
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
return NewRandomFromReader(rander)
}
func NewRandomFromReader(r io.Reader) (UUID, error) {
var uuid UUID
_, err := io.ReadFull(rander, uuid[:])
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
}
@ -36,3 +40,4 @@ func NewRandom() (UUID, error) {
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@ -48,7 +48,7 @@ func main() {
// We can use the Contains helpers to check if an error contains
// another error. It is safe to do this with a nil error, or with
// an error that doesn't even use the errwrap package.
if errwrap.Contains(err, ErrNotExist) {
if errwrap.Contains(err, "does not exist") {
// Do something
}
if errwrap.ContainsType(err, new(os.PathError)) {

1
vendor/github.com/hashicorp/errwrap/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/hashicorp/errwrap

1
vendor/github.com/hashicorp/go-cleanhttp/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/hashicorp/go-cleanhttp

View File

@ -27,17 +27,22 @@ func PrintablePathCheckHandler(next http.Handler, input *HandlerInput) http.Hand
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check URL path for non-printable characters
idx := strings.IndexFunc(r.URL.Path, func(c rune) bool {
return !unicode.IsPrint(c)
})
if r != nil {
// Check URL path for non-printable characters
idx := strings.IndexFunc(r.URL.Path, func(c rune) bool {
return !unicode.IsPrint(c)
})
if idx != -1 {
w.WriteHeader(input.ErrStatus)
return
if idx != -1 {
w.WriteHeader(input.ErrStatus)
return
}
if next != nil {
next.ServeHTTP(w, r)
}
}
next.ServeHTTP(w, r)
return
})
}

View File

@ -14,13 +14,16 @@ makes `retryablehttp` very easy to drop into existing programs.
`retryablehttp` performs automatic retries under certain conditions. Mainly, if
an error is returned by the client (connection errors, etc.), or if a 500-range
response code is received, then a retry is invoked after a wait period.
Otherwise, the response is returned and left to the caller to interpret.
response code is received (except 501), then a retry is invoked after a wait
period. Otherwise, the response is returned and left to the caller to
interpret.
The main difference from `net/http` is that requests which take a request body
(POST/PUT et. al) require an `io.ReadSeeker` to be provided. This enables the
request body to be "rewound" if the initial request fails so that the full
request can be attempted again.
(POST/PUT et. al) can have the body provided in a number of ways (some more or
less efficient) that allow "rewinding" the request body if the initial request
fails so that the full request can be attempted again. See the
[godoc](http://godoc.org/github.com/hashicorp/go-retryablehttp) for more
details.
Example Use
===========

View File

@ -8,13 +8,22 @@
// response is received, then a retry is invoked. Otherwise, the response is
// returned and left to the caller to interpret.
//
// The main difference from net/http is that requests which take a request body
// (POST/PUT et. al) require an io.ReadSeeker to be provided. This enables the
// request body to be "rewound" if the initial request fails so that the full
// request can be attempted again.
// Requests which take a request body should provide a non-nil function
// parameter. The best choice is to provide either a function satisfying
// ReaderFunc which provides multiple io.Readers in an efficient manner, a
// *bytes.Buffer (the underlying raw byte slice will be used) or a raw byte
// slice. As it is a reference type, and we will wrap it as needed by readers,
// we can efficiently re-use the request body without needing to copy it. If an
// io.Reader (such as a *bytes.Reader) is provided, the full body will be read
// prior to the first request, and will be efficiently re-used for any retries.
// ReadSeeker can be used, but some users have observed occasional data races
// between the net/http library and the Seek functionality of some
// implementations of ReadSeeker, so should be avoided if possible.
package retryablehttp
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
@ -27,7 +36,7 @@ import (
"strings"
"time"
"github.com/hashicorp/go-cleanhttp"
cleanhttp "github.com/hashicorp/go-cleanhttp"
)
var (
@ -45,6 +54,9 @@ var (
respReadLimit = int64(4096)
)
// ReaderFunc is the type of function that can be given natively to NewRequest
type ReaderFunc func() (io.Reader, error)
// LenReader is an interface implemented by many in-memory io.Reader's. Used
// for automatically sending the right Content-Length header when possible.
type LenReader interface {
@ -55,48 +67,179 @@ type LenReader interface {
type Request struct {
// body is a seekable reader over the request body payload. This is
// used to rewind the request data in between retries.
body io.ReadSeeker
body ReaderFunc
// Embed an HTTP request directly. This makes a *Request act exactly
// like an *http.Request so that all meta methods are supported.
*http.Request
}
// NewRequest creates a new wrapped request.
func NewRequest(method, url string, body io.ReadSeeker) (*Request, error) {
// Wrap the body in a noop ReadCloser if non-nil. This prevents the
// reader from being closed by the HTTP client.
var rcBody io.ReadCloser
if body != nil {
rcBody = ioutil.NopCloser(body)
}
// WithContext returns wrapped Request with a shallow copy of underlying *http.Request
// with its context changed to ctx. The provided ctx must be non-nil.
func (r *Request) WithContext(ctx context.Context) *Request {
r.Request = r.Request.WithContext(ctx)
return r
}
// Make the request with the noop-closer for the body.
httpReq, err := http.NewRequest(method, url, rcBody)
// BodyBytes allows accessing the request body. It is an analogue to
// http.Request's Body variable, but it returns a copy of the underlying data
// rather than consuming it.
//
// This function is not thread-safe; do not call it at the same time as another
// call, or at the same time this request is being used with Client.Do.
func (r *Request) BodyBytes() ([]byte, error) {
if r.body == nil {
return nil, nil
}
body, err := r.body()
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(body)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func getBodyReaderAndContentLength(rawBody interface{}) (ReaderFunc, int64, error) {
var bodyReader ReaderFunc
var contentLength int64
if rawBody != nil {
switch body := rawBody.(type) {
// If they gave us a function already, great! Use it.
case ReaderFunc:
bodyReader = body
tmp, err := body()
if err != nil {
return nil, 0, err
}
if lr, ok := tmp.(LenReader); ok {
contentLength = int64(lr.Len())
}
if c, ok := tmp.(io.Closer); ok {
c.Close()
}
case func() (io.Reader, error):
bodyReader = body
tmp, err := body()
if err != nil {
return nil, 0, err
}
if lr, ok := tmp.(LenReader); ok {
contentLength = int64(lr.Len())
}
if c, ok := tmp.(io.Closer); ok {
c.Close()
}
// If a regular byte slice, we can read it over and over via new
// readers
case []byte:
buf := body
bodyReader = func() (io.Reader, error) {
return bytes.NewReader(buf), nil
}
contentLength = int64(len(buf))
// If a bytes.Buffer we can read the underlying byte slice over and
// over
case *bytes.Buffer:
buf := body
bodyReader = func() (io.Reader, error) {
return bytes.NewReader(buf.Bytes()), nil
}
contentLength = int64(buf.Len())
// We prioritize *bytes.Reader here because we don't really want to
// deal with it seeking so want it to match here instead of the
// io.ReadSeeker case.
case *bytes.Reader:
buf, err := ioutil.ReadAll(body)
if err != nil {
return nil, 0, err
}
bodyReader = func() (io.Reader, error) {
return bytes.NewReader(buf), nil
}
contentLength = int64(len(buf))
// Compat case
case io.ReadSeeker:
raw := body
bodyReader = func() (io.Reader, error) {
_, err := raw.Seek(0, 0)
return ioutil.NopCloser(raw), err
}
if lr, ok := raw.(LenReader); ok {
contentLength = int64(lr.Len())
}
// Read all in so we can reset
case io.Reader:
buf, err := ioutil.ReadAll(body)
if err != nil {
return nil, 0, err
}
bodyReader = func() (io.Reader, error) {
return bytes.NewReader(buf), nil
}
contentLength = int64(len(buf))
default:
return nil, 0, fmt.Errorf("cannot handle type %T", rawBody)
}
}
return bodyReader, contentLength, nil
}
// FromRequest wraps an http.Request in a retryablehttp.Request
func FromRequest(r *http.Request) (*Request, error) {
bodyReader, _, err := getBodyReaderAndContentLength(r.Body)
if err != nil {
return nil, err
}
// Could assert contentLength == r.ContentLength
return &Request{bodyReader, r}, nil
}
// NewRequest creates a new wrapped request.
func NewRequest(method, url string, rawBody interface{}) (*Request, error) {
bodyReader, contentLength, err := getBodyReaderAndContentLength(rawBody)
if err != nil {
return nil, err
}
// Check if we can set the Content-Length automatically.
if lr, ok := body.(LenReader); ok {
httpReq.ContentLength = int64(lr.Len())
httpReq, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
httpReq.ContentLength = contentLength
return &Request{body, httpReq}, nil
return &Request{bodyReader, httpReq}, nil
}
// Logger interface allows to use other loggers than
// standard log.Logger.
type Logger interface {
Printf(string, ...interface{})
}
// RequestLogHook allows a function to run before each retry. The HTTP
// request which will be made, and the retry number (0 for the initial
// request) are available to users. The internal logger is exposed to
// consumers.
type RequestLogHook func(*log.Logger, *http.Request, int)
type RequestLogHook func(Logger, *http.Request, int)
// ResponseLogHook is like RequestLogHook, but allows running a function
// on each HTTP response. This function will be invoked at the end of
// every HTTP request executed, regardless of whether a subsequent retry
// needs to be performed or not. If the response body is read or closed
// from this method, this will affect the response returned from Do().
type ResponseLogHook func(*log.Logger, *http.Response)
type ResponseLogHook func(Logger, *http.Response)
// CheckRetry specifies a policy for handling retries. It is called
// following each request with the response and error values returned by
@ -106,7 +249,7 @@ type ResponseLogHook func(*log.Logger, *http.Response)
// Client will close any response body when retrying, but if the retry is
// aborted it is up to the CheckResponse callback to properly close any
// response body before returning.
type CheckRetry func(resp *http.Response, err error) (bool, error)
type CheckRetry func(ctx context.Context, resp *http.Response, err error) (bool, error)
// Backoff specifies a policy for how long to wait between retries.
// It is called after a failing request to determine the amount of time
@ -123,7 +266,7 @@ type ErrorHandler func(resp *http.Response, err error, numTries int) (*http.Resp
// like automatic retries to tolerate minor outages.
type Client struct {
HTTPClient *http.Client // Internal HTTP client.
Logger *log.Logger // Customer logger instance.
Logger Logger // Customer logger instance.
RetryWaitMin time.Duration // Minimum time to wait
RetryWaitMax time.Duration // Maximum time to wait
@ -163,7 +306,12 @@ func NewClient() *Client {
// DefaultRetryPolicy provides a default callback for Client.CheckRetry, which
// will retry on connection errors and server errors.
func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) {
func DefaultRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) {
// do not retry on context.Canceled or context.DeadlineExceeded
if ctx.Err() != nil {
return false, ctx.Err()
}
if err != nil {
return true, err
}
@ -171,7 +319,7 @@ func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) {
// the server time to recover, as 500's are typically not permanent
// errors and may relate to outages on the server side. This will catch
// invalid response codes as well, like 0 and 999.
if resp.StatusCode == 0 || resp.StatusCode >= 500 {
if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != 501) {
return true, nil
}
@ -249,8 +397,14 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
// Always rewind the request body when non-nil.
if req.body != nil {
if _, err := req.body.Seek(0, 0); err != nil {
return nil, fmt.Errorf("failed to seek body: %v", err)
body, err := req.body()
if err != nil {
return resp, err
}
if c, ok := body.(io.ReadCloser); ok {
req.Body = c
} else {
req.Body = ioutil.NopCloser(body)
}
}
@ -265,7 +419,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
}
// Check if we should continue with retries.
checkOK, checkErr := c.CheckRetry(resp, err)
checkOK, checkErr := c.CheckRetry(req.Context(), resp, err)
if err != nil {
if c.Logger != nil {
@ -291,7 +445,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
// We do this before drainBody beause there's no need for the I/O if
// we're breaking out
remain := c.RetryMax - i
if remain == 0 {
if remain <= 0 {
break
}
@ -308,7 +462,11 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
if c.Logger != nil {
c.Logger.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain)
}
time.Sleep(wait)
select {
case <-req.Context().Done():
return nil, req.Context().Err()
case <-time.After(wait):
}
}
if c.ErrorHandler != nil {
@ -364,12 +522,12 @@ func (c *Client) Head(url string) (*http.Response, error) {
}
// Post is a shortcut for doing a POST request without making a new client.
func Post(url, bodyType string, body io.ReadSeeker) (*http.Response, error) {
func Post(url, bodyType string, body interface{}) (*http.Response, error) {
return defaultClient.Post(url, bodyType, body)
}
// Post is a convenience method for doing simple POST requests.
func (c *Client) Post(url, bodyType string, body io.ReadSeeker) (*http.Response, error) {
func (c *Client) Post(url, bodyType string, body interface{}) (*http.Response, error) {
req, err := NewRequest("POST", url, body)
if err != nil {
return nil, err

3
vendor/github.com/hashicorp/go-retryablehttp/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/hashicorp/go-retryablehttp
require github.com/hashicorp/go-cleanhttp v0.5.0

2
vendor/github.com/hashicorp/go-retryablehttp/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=

5
vendor/github.com/hashicorp/go-rootcerts/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/hashicorp/go-rootcerts
go 1.12
require github.com/mitchellh/go-homedir v1.1.0

2
vendor/github.com/hashicorp/go-rootcerts/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=

View File

@ -55,7 +55,7 @@ doc::
world::
@set -e; \
for os in solaris darwin freebsd linux windows; do \
for os in solaris darwin freebsd linux windows android; do \
for arch in amd64; do \
printf "Building on %s-%s\n" "$${os}" "$${arch}" ; \
env GOOS="$${os}" GOARCH="$${arch}" go build -o /dev/null; \

8
vendor/github.com/hashicorp/go-sockaddr/go.mod generated vendored Normal file
View File

@ -0,0 +1,8 @@
module github.com/hashicorp/go-sockaddr
require (
github.com/hashicorp/errwrap v1.0.0
github.com/mitchellh/cli v1.0.0
github.com/mitchellh/go-wordwrap v1.0.0
github.com/ryanuber/columnize v2.1.0+incompatible
)

24
vendor/github.com/hashicorp/go-sockaddr/go.sum generated vendored Normal file
View File

@ -0,0 +1,24 @@
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
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/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc h1:MeuS1UDyZyFH++6vVy44PuufTeFF0d0nfI6XB87YGSk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -16,7 +16,7 @@ var (
// Centralize all regexps and regexp.Copy() where necessary.
signRE *regexp.Regexp = regexp.MustCompile(`^[\s]*[+-]`)
whitespaceRE *regexp.Regexp = regexp.MustCompile(`[\s]+`)
ifNameRE *regexp.Regexp = regexp.MustCompile(`^Ethernet adapter ([^:]+):`)
ifNameRE *regexp.Regexp = regexp.MustCompile(`^(?:Ethernet|Wireless LAN) adapter ([^:]+):`)
ipAddrRE *regexp.Regexp = regexp.MustCompile(`^ IPv[46] Address\. \. \. \. \. \. \. \. \. \. \. : ([^\s]+)`)
)
@ -1197,18 +1197,12 @@ func parseDefaultIfNameFromRoute(routeOut string) (string, error) {
// parseDefaultIfNameFromIPCmd parses the default interface from ip(8) for
// Linux.
func parseDefaultIfNameFromIPCmd(routeOut string) (string, error) {
lines := strings.Split(routeOut, "\n")
re := whitespaceRE.Copy()
for _, line := range lines {
kvs := re.Split(line, -1)
if len(kvs) < 5 {
continue
}
if kvs[0] == "default" &&
kvs[1] == "via" &&
kvs[3] == "dev" {
ifName := strings.TrimSpace(kvs[4])
parsedLines := parseIfNameFromIPCmd(routeOut)
for _, parsedLine := range parsedLines {
if parsedLine[0] == "default" &&
parsedLine[1] == "via" &&
parsedLine[3] == "dev" {
ifName := strings.TrimSpace(parsedLine[4])
return ifName, nil
}
}
@ -1216,6 +1210,35 @@ func parseDefaultIfNameFromIPCmd(routeOut string) (string, error) {
return "", errors.New("No default interface found")
}
// parseDefaultIfNameFromIPCmdAndroid parses the default interface from ip(8) for
// Android.
func parseDefaultIfNameFromIPCmdAndroid(routeOut string) (string, error) {
parsedLines := parseIfNameFromIPCmd(routeOut)
if (len(parsedLines) > 0) {
ifName := strings.TrimSpace(parsedLines[0][4])
return ifName, nil
}
return "", errors.New("No default interface found")
}
// parseIfNameFromIPCmd parses interfaces from ip(8) for
// Linux.
func parseIfNameFromIPCmd(routeOut string) [][]string {
lines := strings.Split(routeOut, "\n")
re := whitespaceRE.Copy()
parsedLines := make([][]string, 0, len(lines))
for _, line := range lines {
kvs := re.Split(line, -1)
if len(kvs) < 5 {
continue
}
parsedLines = append(parsedLines, kvs)
}
return parsedLines
}
// parseDefaultIfNameWindows parses the default interface from `netstat -rn` and
// `ipconfig` on Windows.
func parseDefaultIfNameWindows(routeOut, ipconfigOut string) (string, error) {

View File

@ -0,0 +1,34 @@
package sockaddr
import (
"errors"
"os/exec"
)
type routeInfo struct {
cmds map[string][]string
}
// NewRouteInfo returns a Android-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
return routeInfo{
cmds: map[string][]string{"ip": {"/system/bin/ip", "route", "get", "8.8.8.8"}},
}, nil
}
// GetDefaultInterfaceName returns the interface name attached to the default
// route on the default interface.
func (ri routeInfo) GetDefaultInterfaceName() (string, error) {
out, err := exec.Command(ri.cmds["ip"][0], ri.cmds["ip"][1:]...).Output()
if err != nil {
return "", err
}
var ifName string
if ifName, err = parseDefaultIfNameFromIPCmdAndroid(string(out)); err != nil {
return "", errors.New("No default interface found")
}
return ifName, nil
}

View File

@ -1,3 +1,5 @@
// +build !android
package sockaddr
import (

View File

@ -1,5 +1,7 @@
package api
import "context"
// TokenAuth is used to perform token backend operations on Vault
type TokenAuth struct {
c *Client
@ -16,7 +18,9 @@ func (c *TokenAuth) Create(opts *TokenCreateRequest) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -31,7 +35,9 @@ func (c *TokenAuth) CreateOrphan(opts *TokenCreateRequest) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -46,7 +52,9 @@ func (c *TokenAuth) CreateWithRole(opts *TokenCreateRequest, roleName string) (*
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -63,7 +71,9 @@ func (c *TokenAuth) Lookup(token string) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -79,7 +89,10 @@ func (c *TokenAuth) LookupAccessor(accessor string) (*Secret, error) {
}); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -91,7 +104,9 @@ func (c *TokenAuth) LookupAccessor(accessor string) (*Secret, error) {
func (c *TokenAuth) LookupSelf() (*Secret, error) {
r := c.c.NewRequest("GET", "/v1/auth/token/lookup-self")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -109,7 +124,9 @@ func (c *TokenAuth) Renew(token string, increment int) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -126,7 +143,9 @@ func (c *TokenAuth) RenewSelf(increment int) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -146,7 +165,9 @@ func (c *TokenAuth) RenewTokenAsSelf(token string, increment int) (*Secret, erro
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -164,7 +185,10 @@ func (c *TokenAuth) RevokeAccessor(accessor string) error {
}); err != nil {
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -183,7 +207,9 @@ func (c *TokenAuth) RevokeOrphan(token string) error {
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -197,7 +223,10 @@ func (c *TokenAuth) RevokeOrphan(token string) error {
// an effect.
func (c *TokenAuth) RevokeSelf(token string) error {
r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-self")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -217,7 +246,9 @@ func (c *TokenAuth) RevokeTree(token string) error {
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -240,4 +271,6 @@ type TokenCreateRequest struct {
DisplayName string `json:"display_name"`
NumUses int `json:"num_uses"`
Renewable *bool `json:"renewable,omitempty"`
Type string `json:"type"`
EntityAlias string `json:"entity_alias"`
}

View File

@ -16,21 +16,24 @@ import (
"unicode"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
cleanhttp "github.com/hashicorp/go-cleanhttp"
retryablehttp "github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/vault/helper/parseutil"
rootcerts "github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/parseutil"
"golang.org/x/net/http2"
"golang.org/x/time/rate"
)
const EnvVaultAddress = "VAULT_ADDR"
const EnvVaultAgentAddr = "VAULT_AGENT_ADDR"
const EnvVaultCACert = "VAULT_CACERT"
const EnvVaultCAPath = "VAULT_CAPATH"
const EnvVaultClientCert = "VAULT_CLIENT_CERT"
const EnvVaultClientKey = "VAULT_CLIENT_KEY"
const EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
const EnvVaultSkipVerify = "VAULT_SKIP_VERIFY"
const EnvVaultNamespace = "VAULT_NAMESPACE"
const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
const EnvVaultWrapTTL = "VAULT_WRAP_TTL"
const EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
@ -38,6 +41,10 @@ const EnvVaultToken = "VAULT_TOKEN"
const EnvVaultMFA = "VAULT_MFA"
const EnvRateLimit = "VAULT_RATE_LIMIT"
// Deprecated values
const EnvVaultAgentAddress = "VAULT_AGENT_ADDR"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
// WrappingLookupFunc is a function that, given an HTTP verb and a path,
// returns an optional string duration to be used for response wrapping (e.g.
// "15s", or simply "15"). The path will not begin with "/v1/" or "v1/" or "/",
@ -55,6 +62,10 @@ type Config struct {
// HttpClient.
Address string
// AgentAddress is the address of the local Vault agent. This should be a
// complete URL such as "http://vault.example.com".
AgentAddress string
// HttpClient is the HTTP client to use. Vault sets sane defaults for the
// http.Client and its associated http.Transport created in DefaultConfig.
// If you must modify Vault's defaults, it is suggested that you start with
@ -83,6 +94,14 @@ type Config struct {
// then that limiter will be used. Note that an empty Limiter
// is equivalent blocking all events.
Limiter *rate.Limiter
// OutputCurlString causes the actual request to return an error of type
// *OutputStringError. Type asserting the error message will allow
// fetching a cURL-compatible string for the operation.
//
// Note: It is not thread-safe to set this and make concurrent requests
// with the same client. Cloning a client will not clone this value.
OutputCurlString bool
}
// TLSConfig contains the parameters needed to configure TLS on the HTTP client
@ -120,7 +139,7 @@ type TLSConfig struct {
func DefaultConfig() *Config {
config := &Config{
Address: "https://127.0.0.1:8200",
HttpClient: cleanhttp.DefaultClient(),
HttpClient: cleanhttp.DefaultPooledClient(),
}
config.HttpClient.Timeout = time.Second * 60
@ -214,6 +233,7 @@ func (c *Config) ConfigureTLS(t *TLSConfig) error {
// there is an error, no configuration value is updated.
func (c *Config) ReadEnvironment() error {
var envAddress string
var envAgentAddress string
var envCACert string
var envCAPath string
var envClientCert string
@ -228,6 +248,11 @@ func (c *Config) ReadEnvironment() error {
if v := os.Getenv(EnvVaultAddress); v != "" {
envAddress = v
}
if v := os.Getenv(EnvVaultAgentAddr); v != "" {
envAgentAddress = v
} else if v := os.Getenv(EnvVaultAgentAddress); v != "" {
envAgentAddress = v
}
if v := os.Getenv(EnvVaultMaxRetries); v != "" {
maxRetries, err := strconv.ParseUint(v, 10, 32)
if err != nil {
@ -261,13 +286,20 @@ func (c *Config) ReadEnvironment() error {
}
envClientTimeout = clientTimeout
}
if v := os.Getenv(EnvVaultInsecure); v != "" {
if v := os.Getenv(EnvVaultSkipVerify); v != "" {
var err error
envInsecure, err = strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("could not parse VAULT_SKIP_VERIFY")
}
} else if v := os.Getenv(EnvVaultInsecure); v != "" {
var err error
envInsecure, err = strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("could not parse VAULT_INSECURE")
}
}
if v := os.Getenv(EnvVaultTLSServerName); v != "" {
envTLSServerName = v
}
@ -295,6 +327,10 @@ func (c *Config) ReadEnvironment() error {
c.Address = envAddress
}
if envAgentAddress != "" {
c.AgentAddress = envAgentAddress
}
if envMaxRetries != nil {
c.MaxRetries = int(*envMaxRetries)
}
@ -357,11 +393,6 @@ func NewClient(c *Config) (*Client, error) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
u, err := url.Parse(c.Address)
if err != nil {
return nil, err
}
if c.HttpClient == nil {
c.HttpClient = def.HttpClient
}
@ -369,6 +400,32 @@ func NewClient(c *Config) (*Client, error) {
c.HttpClient.Transport = def.HttpClient.Transport
}
address := c.Address
if c.AgentAddress != "" {
address = c.AgentAddress
}
u, err := url.Parse(address)
if err != nil {
return nil, err
}
if strings.HasPrefix(address, "unix://") {
socket := strings.TrimPrefix(address, "unix://")
transport := c.HttpClient.Transport.(*http.Transport)
transport.DialContext = func(context.Context, string, string) (net.Conn, error) {
return net.Dial("unix", socket)
}
// Since the address points to a unix domain socket, the scheme in the
// *URL would be set to `unix`. The *URL in the client is expected to
// be pointing to the protocol used in the application layer and not to
// the transport layer. Hence, setting the fields accordingly.
u.Scheme = "http"
u.Host = socket
u.Path = ""
}
client := &Client{
addr: u,
config: c,
@ -378,6 +435,10 @@ func NewClient(c *Config) (*Client, error) {
client.token = token
}
if namespace := os.Getenv(EnvVaultNamespace); namespace != "" {
client.setNamespace(namespace)
}
return client, nil
}
@ -388,11 +449,12 @@ func (c *Client) SetAddress(addr string) error {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
var err error
if c.addr, err = url.Parse(addr); err != nil {
parsedAddr, err := url.Parse(addr)
if err != nil {
return errwrap.Wrapf("failed to set address: {{err}}", err)
}
c.addr = parsedAddr
return nil
}
@ -411,7 +473,8 @@ func (c *Client) SetLimiter(rateLimit float64, burst int) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
defer c.modifyLock.RUnlock()
c.modifyLock.RUnlock()
c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst)
}
@ -435,6 +498,24 @@ func (c *Client) SetClientTimeout(timeout time.Duration) {
c.config.Timeout = timeout
}
func (c *Client) OutputCurlString() bool {
c.modifyLock.RLock()
c.config.modifyLock.RLock()
defer c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock()
return c.config.OutputCurlString
}
func (c *Client) SetOutputCurlString(curl bool) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
c.config.OutputCurlString = curl
}
// CurrentWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
// for a given operation and path
func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc {
@ -462,6 +543,22 @@ func (c *Client) SetMFACreds(creds []string) {
c.mfaCreds = creds
}
// SetNamespace sets the namespace supplied either via the environment
// variable or via the command line.
func (c *Client) SetNamespace(namespace string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.setNamespace(namespace)
}
func (c *Client) setNamespace(namespace string) {
if c.headers == nil {
c.headers = make(http.Header)
}
c.headers.Set(consts.NamespaceHeaderName, namespace)
}
// Token returns the access token being used by this client. It will
// return the empty string if there is no token set.
func (c *Client) Token() string {
@ -488,6 +585,26 @@ func (c *Client) ClearToken() {
c.token = ""
}
// Headers gets the current set of headers used for requests. This returns a
// copy; to modify it make modifications locally and use SetHeaders.
func (c *Client) Headers() http.Header {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
if c.headers == nil {
return nil
}
ret := make(http.Header)
for k, v := range c.headers {
for _, val := range v {
ret[k] = append(ret[k], val)
}
}
return ret
}
// SetHeaders sets the headers to be used for future requests.
func (c *Client) SetHeaders(headers http.Header) {
c.modifyLock.Lock()
@ -510,6 +627,10 @@ func (c *Client) SetBackoff(backoff retryablehttp.Backoff) {
// underlying http.Client is used; modifying the client from more than one
// goroutine at once may not be safe, so modify the client as needed and then
// clone.
//
// Also, only the client's config is currently copied; this means items not in
// the api.Config struct, such as policy override and wrapping function
// behavior, must currently then be set as desired on the new client.
func (c *Client) Clone() (*Client, error) {
c.modifyLock.RLock()
c.config.modifyLock.RLock()
@ -544,14 +665,20 @@ func (c *Client) SetPolicyOverride(override bool) {
// doesn't need to be called externally.
func (c *Client) NewRequest(method, requestPath string) *Request {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
addr := c.addr
token := c.token
mfaCreds := c.mfaCreds
wrappingLookupFunc := c.wrappingLookupFunc
headers := c.headers
policyOverride := c.policyOverride
c.modifyLock.RUnlock()
// if SRV records exist (see https://tools.ietf.org/html/draft-andrews-http-srv-02), lookup the SRV
// record and take the highest match; this is not designed for high-availability, just discovery
var host string = c.addr.Host
if c.addr.Port() == "" {
var host string = addr.Host
if addr.Port() == "" {
// Internet Draft specifies that the SRV record is ignored if a port is given
_, addrs, err := net.LookupSRV("http", "tcp", c.addr.Hostname())
_, addrs, err := net.LookupSRV("http", "tcp", addr.Hostname())
if err == nil && len(addrs) > 0 {
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
}
@ -560,12 +687,12 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
req := &Request{
Method: method,
URL: &url.URL{
User: c.addr.User,
Scheme: c.addr.Scheme,
User: addr.User,
Scheme: addr.Scheme,
Host: host,
Path: path.Join(c.addr.Path, requestPath),
Path: path.Join(addr.Path, requestPath),
},
ClientToken: c.token,
ClientToken: token,
Params: make(map[string][]string),
}
@ -579,21 +706,19 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
lookupPath = requestPath
}
req.MFAHeaderVals = c.mfaCreds
req.MFAHeaderVals = mfaCreds
if c.wrappingLookupFunc != nil {
req.WrapTTL = c.wrappingLookupFunc(method, lookupPath)
if wrappingLookupFunc != nil {
req.WrapTTL = wrappingLookupFunc(method, lookupPath)
} else {
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
}
if c.config.Timeout != 0 {
c.config.HttpClient.Timeout = c.config.Timeout
}
if c.headers != nil {
req.Headers = c.headers
if headers != nil {
req.Headers = headers
}
req.PolicyOverride = c.policyOverride
req.PolicyOverride = policyOverride
return req
}
@ -602,18 +727,31 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
// a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally.
func (c *Client) RawRequest(r *Request) (*Response, error) {
return c.RawRequestWithContext(context.Background(), r)
}
// RawRequestWithContext performs the raw request given. This request may be against
// a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally.
func (c *Client) RawRequestWithContext(ctx context.Context, r *Request) (*Response, error) {
c.modifyLock.RLock()
c.config.modifyLock.RLock()
defer c.config.modifyLock.RUnlock()
if c.config.Limiter != nil {
c.config.Limiter.Wait(context.Background())
}
token := c.token
c.config.modifyLock.RLock()
limiter := c.config.Limiter
maxRetries := c.config.MaxRetries
backoff := c.config.Backoff
httpClient := c.config.HttpClient
timeout := c.config.Timeout
outputCurlString := c.config.OutputCurlString
c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock()
if limiter != nil {
limiter.Wait(ctx)
}
// Sanity check the token before potentially erroring from the API
idx := strings.IndexFunc(token, func(c rune) bool {
return !unicode.IsPrint(c)
@ -624,7 +762,7 @@ func (c *Client) RawRequest(r *Request) (*Response, error) {
redirectCount := 0
START:
req, err := r.toRetryableHTTP(false)
req, err := r.toRetryableHTTP()
if err != nil {
return nil, err
}
@ -632,16 +770,25 @@ START:
return nil, fmt.Errorf("nil request created")
}
backoff := c.config.Backoff
if outputCurlString {
LastOutputStringError = &OutputStringError{Request: req}
return nil, LastOutputStringError
}
if timeout != 0 {
ctx, _ = context.WithTimeout(ctx, timeout)
}
req.Request = req.Request.WithContext(ctx)
if backoff == nil {
backoff = retryablehttp.LinearJitterBackoff
}
client := &retryablehttp.Client{
HTTPClient: c.config.HttpClient,
HTTPClient: httpClient,
RetryWaitMin: 1000 * time.Millisecond,
RetryWaitMax: 1500 * time.Millisecond,
RetryMax: c.config.MaxRetries,
RetryMax: maxRetries,
CheckRetry: retryablehttp.DefaultRetryPolicy,
Backoff: backoff,
ErrorHandler: retryablehttp.PassthroughErrorHandler,

19
vendor/github.com/hashicorp/vault/api/go.mod generated vendored Normal file
View File

@ -0,0 +1,19 @@
module github.com/hashicorp/vault/api
go 1.12
replace github.com/hashicorp/vault/sdk => ../sdk
require (
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/go-retryablehttp v0.5.4
github.com/hashicorp/go-rootcerts v1.0.1
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/vault/sdk v0.1.13
github.com/mitchellh/mapstructure v1.1.2
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
gopkg.in/square/go-jose.v2 v2.3.1
)

118
vendor/github.com/hashicorp/vault/api/go.sum generated vendored Normal file
View File

@ -0,0 +1,118 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
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/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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-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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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/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/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-20180823144017-11551d06cbcc/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-20190129075346-302c3dd5f1cc/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.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-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"fmt"
)
@ -8,7 +9,10 @@ import (
func (c *Client) Help(path string) (*Help, error) {
r := c.NewRequest("GET", fmt.Sprintf("/v1/%s", path))
r.Params.Add("help", "1")
resp, err := c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -20,6 +24,7 @@ func (c *Client) Help(path string) (*Help, error) {
}
type Help struct {
Help string `json:"help"`
SeeAlso []string `json:"see_also"`
Help string `json:"help"`
SeeAlso []string `json:"see_also"`
OpenAPI map[string]interface{} `json:"openapi"`
}

View File

@ -2,12 +2,14 @@ package api
import (
"bytes"
"context"
"fmt"
"io"
"net/url"
"os"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
)
const (
@ -45,8 +47,29 @@ func (c *Client) Logical() *Logical {
}
func (c *Logical) Read(path string) (*Secret, error) {
return c.ReadWithData(path, nil)
}
func (c *Logical) ReadWithData(path string, data map[string][]string) (*Secret, error) {
r := c.c.NewRequest("GET", "/v1/"+path)
resp, err := c.c.RawRequest(r)
var values url.Values
for k, v := range data {
if values == nil {
values = make(url.Values)
}
for _, val := range v {
values.Add(k, val)
}
}
if values != nil {
r.Params = values
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if resp != nil {
defer resp.Body.Close()
}
@ -77,7 +100,10 @@ func (c *Logical) List(path string) (*Secret, error) {
// handle the wrapping lookup function
r.Method = "GET"
r.Params.Set("list", "true")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if resp != nil {
defer resp.Body.Close()
}
@ -108,7 +134,9 @@ func (c *Logical) Write(path string, data map[string]interface{}) (*Secret, erro
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if resp != nil {
defer resp.Body.Close()
}
@ -129,16 +157,33 @@ func (c *Logical) Write(path string, data map[string]interface{}) (*Secret, erro
return nil, err
}
if resp.StatusCode == 200 {
return ParseSecret(resp.Body)
}
return nil, nil
return ParseSecret(resp.Body)
}
func (c *Logical) Delete(path string) (*Secret, error) {
return c.DeleteWithData(path, nil)
}
func (c *Logical) DeleteWithData(path string, data map[string][]string) (*Secret, error) {
r := c.c.NewRequest("DELETE", "/v1/"+path)
resp, err := c.c.RawRequest(r)
var values url.Values
for k, v := range data {
if values == nil {
values = make(url.Values)
}
for _, val := range v {
values.Add(k, val)
}
}
if values != nil {
r.Params = values
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if resp != nil {
defer resp.Body.Close()
}
@ -159,11 +204,7 @@ func (c *Logical) Delete(path string) (*Secret, error) {
return nil, err
}
if resp.StatusCode == 200 {
return ParseSecret(resp.Body)
}
return nil, nil
return ParseSecret(resp.Body)
}
func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) {
@ -183,7 +224,9 @@ func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if resp != nil {
defer resp.Body.Close()
}

71
vendor/github.com/hashicorp/vault/api/output_string.go generated vendored Normal file
View File

@ -0,0 +1,71 @@
package api
import (
"fmt"
"strings"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
const (
ErrOutputStringRequest = "output a string, please"
)
var (
LastOutputStringError *OutputStringError
)
type OutputStringError struct {
*retryablehttp.Request
parsingError error
parsedCurlString string
}
func (d *OutputStringError) Error() string {
if d.parsedCurlString == "" {
d.parseRequest()
if d.parsingError != nil {
return d.parsingError.Error()
}
}
return ErrOutputStringRequest
}
func (d *OutputStringError) parseRequest() {
body, err := d.Request.BodyBytes()
if err != nil {
d.parsingError = err
return
}
// Build cURL string
d.parsedCurlString = "curl "
if d.Request.Method != "GET" {
d.parsedCurlString = fmt.Sprintf("%s-X %s ", d.parsedCurlString, d.Request.Method)
}
for k, v := range d.Request.Header {
for _, h := range v {
if strings.ToLower(k) == "x-vault-token" {
h = `$(vault print token)`
}
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", d.parsedCurlString, k, h)
}
}
if len(body) > 0 {
// We need to escape single quotes since that's what we're using to
// quote the body
escapedBody := strings.Replace(string(body), "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s-d '%s' ", d.parsedCurlString, escapedBody)
}
d.parsedCurlString = fmt.Sprintf("%s%s", d.parsedCurlString, d.Request.URL.String())
}
func (d *OutputStringError) CurlString() string {
if d.parsedCurlString == "" {
d.parseRequest()
}
return d.parsedCurlString
}

186
vendor/github.com/hashicorp/vault/api/plugin_helpers.go generated vendored Normal file
View File

@ -0,0 +1,186 @@
package api
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"flag"
"net/url"
"os"
squarejwt "gopkg.in/square/go-jose.v2/jwt"
"github.com/hashicorp/errwrap"
)
var (
// PluginMetadataModeEnv is an ENV name used to disable TLS communication
// to bootstrap mounting plugins.
PluginMetadataModeEnv = "VAULT_PLUGIN_METADATA_MODE"
// PluginUnwrapTokenEnv is the ENV name used to pass unwrap tokens to the
// plugin.
PluginUnwrapTokenEnv = "VAULT_UNWRAP_TOKEN"
)
// PluginAPIClientMeta is a helper that plugins can use to configure TLS connections
// back to Vault.
type PluginAPIClientMeta struct {
// These are set by the command line flags.
flagCACert string
flagCAPath string
flagClientCert string
flagClientKey string
flagInsecure bool
}
// FlagSet returns the flag set for configuring the TLS connection
func (f *PluginAPIClientMeta) FlagSet() *flag.FlagSet {
fs := flag.NewFlagSet("vault plugin settings", flag.ContinueOnError)
fs.StringVar(&f.flagCACert, "ca-cert", "", "")
fs.StringVar(&f.flagCAPath, "ca-path", "", "")
fs.StringVar(&f.flagClientCert, "client-cert", "", "")
fs.StringVar(&f.flagClientKey, "client-key", "", "")
fs.BoolVar(&f.flagInsecure, "tls-skip-verify", false, "")
return fs
}
// GetTLSConfig will return a TLSConfig based off the values from the flags
func (f *PluginAPIClientMeta) GetTLSConfig() *TLSConfig {
// If we need custom TLS configuration, then set it
if f.flagCACert != "" || f.flagCAPath != "" || f.flagClientCert != "" || f.flagClientKey != "" || f.flagInsecure {
t := &TLSConfig{
CACert: f.flagCACert,
CAPath: f.flagCAPath,
ClientCert: f.flagClientCert,
ClientKey: f.flagClientKey,
TLSServerName: "",
Insecure: f.flagInsecure,
}
return t
}
return nil
}
// VaultPluginTLSProvider is run inside a plugin and retrieves the response
// wrapped TLS certificate from vault. It returns a configured TLS Config.
func VaultPluginTLSProvider(apiTLSConfig *TLSConfig) func() (*tls.Config, error) {
if os.Getenv(PluginMetadataModeEnv) == "true" {
return nil
}
return func() (*tls.Config, error) {
unwrapToken := os.Getenv(PluginUnwrapTokenEnv)
parsedJWT, err := squarejwt.ParseSigned(unwrapToken)
if err != nil {
return nil, errwrap.Wrapf("error parsing wrapping token: {{err}}", err)
}
var allClaims = make(map[string]interface{})
if err = parsedJWT.UnsafeClaimsWithoutVerification(&allClaims); err != nil {
return nil, errwrap.Wrapf("error parsing claims from wrapping token: {{err}}", err)
}
addrClaimRaw, ok := allClaims["addr"]
if !ok {
return nil, errors.New("could not validate addr claim")
}
vaultAddr, ok := addrClaimRaw.(string)
if !ok {
return nil, errors.New("could not parse addr claim")
}
if vaultAddr == "" {
return nil, errors.New(`no vault api_addr found`)
}
// Sanity check the value
if _, err := url.Parse(vaultAddr); err != nil {
return nil, errwrap.Wrapf("error parsing the vault api_addr: {{err}}", err)
}
// Unwrap the token
clientConf := DefaultConfig()
clientConf.Address = vaultAddr
if apiTLSConfig != nil {
err := clientConf.ConfigureTLS(apiTLSConfig)
if err != nil {
return nil, errwrap.Wrapf("error configuring api client {{err}}", err)
}
}
client, err := NewClient(clientConf)
if err != nil {
return nil, errwrap.Wrapf("error during api client creation: {{err}}", err)
}
secret, err := client.Logical().Unwrap(unwrapToken)
if err != nil {
return nil, errwrap.Wrapf("error during token unwrap request: {{err}}", err)
}
if secret == nil {
return nil, errors.New("error during token unwrap request: secret is nil")
}
// Retrieve and parse the server's certificate
serverCertBytesRaw, ok := secret.Data["ServerCert"].(string)
if !ok {
return nil, errors.New("error unmarshalling certificate")
}
serverCertBytes, err := base64.StdEncoding.DecodeString(serverCertBytesRaw)
if err != nil {
return nil, errwrap.Wrapf("error parsing certificate: {{err}}", err)
}
serverCert, err := x509.ParseCertificate(serverCertBytes)
if err != nil {
return nil, errwrap.Wrapf("error parsing certificate: {{err}}", err)
}
// Retrieve and parse the server's private key
serverKeyB64, ok := secret.Data["ServerKey"].(string)
if !ok {
return nil, errors.New("error unmarshalling certificate")
}
serverKeyRaw, err := base64.StdEncoding.DecodeString(serverKeyB64)
if err != nil {
return nil, errwrap.Wrapf("error parsing certificate: {{err}}", err)
}
serverKey, err := x509.ParseECPrivateKey(serverKeyRaw)
if err != nil {
return nil, errwrap.Wrapf("error parsing certificate: {{err}}", err)
}
// Add CA cert to the cert pool
caCertPool := x509.NewCertPool()
caCertPool.AddCert(serverCert)
// Build a certificate object out of the server's cert and private key.
cert := tls.Certificate{
Certificate: [][]byte{serverCertBytes},
PrivateKey: serverKey,
Leaf: serverCert,
}
// Setup TLS config
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
RootCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
// TLS 1.2 minimum
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cert},
ServerName: serverCert.Subject.CommonName,
}
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
}

View File

@ -8,6 +8,8 @@ import (
"net/http"
"net/url"
"github.com/hashicorp/vault/sdk/helper/consts"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
@ -22,8 +24,14 @@ type Request struct {
MFAHeaderVals []string
WrapTTL string
Obj interface{}
Body io.Reader
BodySize int64
// When possible, use BodyBytes as it is more efficient due to how the
// retry logic works
BodyBytes []byte
// Fallback
Body io.Reader
BodySize int64
// Whether to request overriding soft-mandatory Sentinel policies (RGPs and
// EGPs). If set, the override flag will take effect for all policies
@ -33,67 +41,75 @@ type Request struct {
// SetJSONBody is used to set a request body that is a JSON-encoded value.
func (r *Request) SetJSONBody(val interface{}) error {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
if err := enc.Encode(val); err != nil {
buf, err := json.Marshal(val)
if err != nil {
return err
}
r.Obj = val
r.Body = buf
r.BodySize = int64(buf.Len())
r.BodyBytes = buf
return nil
}
// ResetJSONBody is used to reset the body for a redirect
func (r *Request) ResetJSONBody() error {
if r.Body == nil {
if r.BodyBytes == nil {
return nil
}
return r.SetJSONBody(r.Obj)
}
// ToHTTP turns this request into a valid *http.Request for use with the
// net/http package.
// DEPRECATED: ToHTTP turns this request into a valid *http.Request for use
// with the net/http package.
func (r *Request) ToHTTP() (*http.Request, error) {
req, err := r.toRetryableHTTP(true)
req, err := r.toRetryableHTTP()
if err != nil {
return nil, err
}
switch {
case r.BodyBytes == nil && r.Body == nil:
// No body
case r.BodyBytes != nil:
req.Request.Body = ioutil.NopCloser(bytes.NewReader(r.BodyBytes))
default:
if c, ok := r.Body.(io.ReadCloser); ok {
req.Request.Body = c
} else {
req.Request.Body = ioutil.NopCloser(r.Body)
}
}
return req.Request, nil
}
// legacy indicates whether we want to return a request derived from
// http.NewRequest instead of retryablehttp.NewRequest, so that legacy clents
// that might be using the public ToHTTP method still work
func (r *Request) toRetryableHTTP(legacy bool) (*retryablehttp.Request, error) {
func (r *Request) toRetryableHTTP() (*retryablehttp.Request, error) {
// Encode the query parameters
r.URL.RawQuery = r.Params.Encode()
// Create the HTTP request, defaulting to retryable
var req *retryablehttp.Request
if legacy {
regReq, err := http.NewRequest(r.Method, r.URL.RequestURI(), r.Body)
if err != nil {
return nil, err
}
req = &retryablehttp.Request{
Request: regReq,
}
} else {
var buf []byte
var err error
if r.Body != nil {
buf, err = ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
}
req, err = retryablehttp.NewRequest(r.Method, r.URL.RequestURI(), bytes.NewReader(buf))
if err != nil {
return nil, err
}
var err error
var body interface{}
switch {
case r.BodyBytes == nil && r.Body == nil:
// No body
case r.BodyBytes != nil:
// Use bytes, it's more efficient
body = r.BodyBytes
default:
body = r.Body
}
req, err = retryablehttp.NewRequest(r.Method, r.URL.RequestURI(), body)
if err != nil {
return nil, err
}
req.URL.User = r.URL.User
@ -110,7 +126,7 @@ func (r *Request) toRetryableHTTP(legacy bool) (*retryablehttp.Request, error) {
}
if len(r.ClientToken) != 0 {
req.Header.Set("X-Vault-Token", r.ClientToken)
req.Header.Set(consts.AuthHeaderName, r.ClientToken)
}
if len(r.WrapTTL) != 0 {

View File

@ -7,7 +7,7 @@ import (
"io/ioutil"
"net/http"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
)
// Response is a raw response that wraps an HTTP response.
@ -42,32 +42,27 @@ func (r *Response) Error() error {
r.Body.Close()
r.Body = ioutil.NopCloser(bodyBuf)
// Build up the error object
respErr := &ResponseError{
HTTPMethod: r.Request.Method,
URL: r.Request.URL.String(),
StatusCode: r.StatusCode,
}
// Decode the error response if we can. Note that we wrap the bodyBuf
// in a bytes.Reader here so that the JSON decoder doesn't move the
// read pointer for the original buffer.
var resp ErrorResponse
if err := jsonutil.DecodeJSON(bodyBuf.Bytes(), &resp); err != nil {
// Ignore the decoding error and just drop the raw response
return fmt.Errorf(
"Error making API request.\n\n"+
"URL: %s %s\n"+
"Code: %d. Raw Message:\n\n%s",
r.Request.Method, r.Request.URL.String(),
r.StatusCode, bodyBuf.String())
// Store the fact that we couldn't decode the errors
respErr.RawError = true
respErr.Errors = []string{bodyBuf.String()}
} else {
// Store the decoded errors
respErr.Errors = resp.Errors
}
var errBody bytes.Buffer
errBody.WriteString(fmt.Sprintf(
"Error making API request.\n\n"+
"URL: %s %s\n"+
"Code: %d. Errors:\n\n",
r.Request.Method, r.Request.URL.String(),
r.StatusCode))
for _, err := range resp.Errors {
errBody.WriteString(fmt.Sprintf("* %s", err))
}
return fmt.Errorf(errBody.String())
return respErr
}
// ErrorResponse is the raw structure of errors when they're returned by the
@ -75,3 +70,51 @@ func (r *Response) Error() error {
type ErrorResponse struct {
Errors []string
}
// ResponseError is the error returned when Vault responds with an error or
// non-success HTTP status code. If a request to Vault fails because of a
// network error a different error message will be returned. ResponseError gives
// access to the underlying errors and status code.
type ResponseError struct {
// HTTPMethod is the HTTP method for the request (PUT, GET, etc).
HTTPMethod string
// URL is the URL of the request.
URL string
// StatusCode is the HTTP status code.
StatusCode int
// RawError marks that the underlying error messages returned by Vault were
// not parsable. The Errors slice will contain the raw response body as the
// first and only error string if this value is set to true.
RawError bool
// Errors are the underlying errors returned by Vault.
Errors []string
}
// Error returns a human-readable error string for the response error.
func (r *ResponseError) Error() string {
errString := "Errors"
if r.RawError {
errString = "Raw Message"
}
var errBody bytes.Buffer
errBody.WriteString(fmt.Sprintf(
"Error making API request.\n\n"+
"URL: %s %s\n"+
"Code: %d. %s:\n\n",
r.HTTPMethod, r.URL, r.StatusCode, errString))
if r.RawError && len(r.Errors) == 1 {
errBody.WriteString(r.Errors[0])
} else {
for _, err := range r.Errors {
errBody.WriteString(fmt.Sprintf("* %s", err))
}
}
return errBody.String()
}

View File

@ -1,13 +1,14 @@
package api
import (
"bytes"
"fmt"
"io"
"time"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/parseutil"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/parseutil"
)
// Secret is the structure returned for every secret within Vault.
@ -101,7 +102,8 @@ func (s *Secret) TokenRemainingUses() (int, error) {
}
// TokenPolicies returns the standardized list of policies for the given secret.
// If the secret is nil or does not contain any policies, this returns nil.
// If the secret is nil or does not contain any policies, this returns nil. It
// also populates the secret's Auth info with identity/token policy info.
func (s *Secret) TokenPolicies() ([]string, error) {
if s == nil {
return nil, nil
@ -115,25 +117,75 @@ func (s *Secret) TokenPolicies() ([]string, error) {
return nil, nil
}
sList, ok := s.Data["policies"].([]string)
if ok {
return sList, nil
}
var tokenPolicies []string
list, ok := s.Data["policies"].([]interface{})
if !ok {
return nil, fmt.Errorf("unable to convert token policies to expected format")
}
policies := make([]string, len(list))
for i := range list {
p, ok := list[i].(string)
// Token policies
{
_, ok := s.Data["policies"]
if !ok {
return nil, fmt.Errorf("unable to convert policy %v to string", list[i])
goto TOKEN_DONE
}
sList, ok := s.Data["policies"].([]string)
if ok {
tokenPolicies = sList
goto TOKEN_DONE
}
list, ok := s.Data["policies"].([]interface{})
if !ok {
return nil, fmt.Errorf("unable to convert token policies to expected format")
}
for _, v := range list {
p, ok := v.(string)
if !ok {
return nil, fmt.Errorf("unable to convert policy %v to string", v)
}
tokenPolicies = append(tokenPolicies, p)
}
policies[i] = p
}
TOKEN_DONE:
var identityPolicies []string
// Identity policies
{
_, ok := s.Data["identity_policies"]
if !ok {
goto DONE
}
sList, ok := s.Data["identity_policies"].([]string)
if ok {
identityPolicies = sList
goto DONE
}
list, ok := s.Data["identity_policies"].([]interface{})
if !ok {
return nil, fmt.Errorf("unable to convert identity policies to expected format")
}
for _, v := range list {
p, ok := v.(string)
if !ok {
return nil, fmt.Errorf("unable to convert policy %v to string", v)
}
identityPolicies = append(identityPolicies, p)
}
}
DONE:
if s.Auth == nil {
s.Auth = &SecretAuth{}
}
policies := append(tokenPolicies, identityPolicies...)
s.Auth.TokenPolicies = tokenPolicies
s.Auth.IdentityPolicies = identityPolicies
s.Auth.Policies = policies
return policies, nil
}
@ -234,10 +286,14 @@ type SecretWrapInfo struct {
// SecretAuth is the structure containing auth information if we have it.
type SecretAuth struct {
ClientToken string `json:"client_token"`
Accessor string `json:"accessor"`
Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"`
ClientToken string `json:"client_token"`
Accessor string `json:"accessor"`
Policies []string `json:"policies"`
TokenPolicies []string `json:"token_policies"`
IdentityPolicies []string `json:"identity_policies"`
Metadata map[string]string `json:"metadata"`
Orphan bool `json:"orphan"`
EntityID string `json:"entity_id"`
LeaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"`
@ -245,9 +301,20 @@ type SecretAuth struct {
// ParseSecret is used to parse a secret value from JSON from an io.Reader.
func ParseSecret(r io.Reader) (*Secret, error) {
// First read the data into a buffer. Not super efficient but we want to
// know if we actually have a body or not.
var buf bytes.Buffer
_, err := buf.ReadFrom(r)
if err != nil {
return nil, err
}
if buf.Len() == 0 {
return nil, nil
}
// First decode the JSON into a map[string]interface{}
var secret Secret
if err := jsonutil.DecodeJSONFromReader(r, &secret); err != nil {
if err := jsonutil.DecodeJSONFromReader(&buf, &secret); err != nil {
return nil, err
}

View File

@ -1,6 +1,9 @@
package api
import "fmt"
import (
"context"
"fmt"
)
// SSH is used to return a client to invoke operations on SSH backend.
type SSH struct {
@ -28,7 +31,9 @@ func (c *SSH) Credential(role string, data map[string]interface{}) (*Secret, err
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -45,7 +50,9 @@ func (c *SSH) SignKey(role string, data map[string]interface{}) (*Secret, error)
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
@ -8,11 +9,12 @@ import (
"os"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-rootcerts"
cleanhttp "github.com/hashicorp/go-cleanhttp"
multierror "github.com/hashicorp/go-multierror"
rootcerts "github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/vault/sdk/helper/hclutil"
"github.com/mitchellh/mapstructure"
)
@ -160,7 +162,7 @@ func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) {
"tls_skip_verify",
"tls_server_name",
}
if err := checkHCLKeys(list, valid); err != nil {
if err := hclutil.CheckHCLKeys(list, valid); err != nil {
return nil, multierror.Prefix(err, "ssh_helper:")
}
@ -206,7 +208,9 @@ func (c *SSHHelper) Verify(otp string) (*SSHVerifyResponse, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -228,30 +232,3 @@ func (c *SSHHelper) Verify(otp string) (*SSHVerifyResponse, error) {
}
return &verifyResp, nil
}
func checkHCLKeys(node ast.Node, valid []string) error {
var list *ast.ObjectList
switch n := node.(type) {
case *ast.ObjectList:
list = n
case *ast.ObjectType:
list = n.List
default:
return fmt.Errorf("cannot check HCL keys of type %T", n)
}
validMap := make(map[string]struct{}, len(valid))
for _, v := range valid {
validMap[v] = struct{}{}
}
var result error
for _, item := range list.Items {
key := item.Keys[0].Token.Value().(string)
if _, ok := validMap[key]; !ok {
result = multierror.Append(result, fmt.Errorf("invalid key %q on line %d", key, item.Assign.Line))
}
}
return result
}

View File

@ -1,6 +1,8 @@
package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -16,56 +18,58 @@ func (c *Sys) AuditHash(path string, input string) (string, error) {
return "", err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return "", err
}
defer resp.Body.Close()
type d struct {
Hash string `json:"hash"`
}
var result d
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return "", err
}
if secret == nil || secret.Data == nil {
return "", errors.New("data from server response is empty")
}
return result.Hash, err
hash, ok := secret.Data["hash"]
if !ok {
return "", errors.New("hash not found in response data")
}
hashStr, ok := hash.(string)
if !ok {
return "", errors.New("could not parse hash in response data")
}
return hashStr, nil
}
func (c *Sys) ListAudit() (map[string]*Audit, error) {
r := c.c.NewRequest("GET", "/v1/sys/audit")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
mounts := map[string]*Audit{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res Audit
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
return mounts, nil
@ -87,7 +91,10 @@ func (c *Sys) EnableAuditWithOptions(path string, options *EnableAuditOptions) e
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -98,7 +105,11 @@ func (c *Sys) EnableAuditWithOptions(path string, options *EnableAuditOptions) e
func (c *Sys) DisableAudit(path string) error {
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/audit/%s", path))
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -110,16 +121,16 @@ func (c *Sys) DisableAudit(path string) error {
// documentation. Please refer to that documentation for more details.
type EnableAuditOptions struct {
Type string `json:"type"`
Description string `json:"description"`
Options map[string]string `json:"options"`
Local bool `json:"local"`
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Options map[string]string `json:"options" mapstructure:"options"`
Local bool `json:"local" mapstructure:"local"`
}
type Audit struct {
Path string
Type string
Description string
Options map[string]string
Local bool
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Options map[string]string `json:"options" mapstructure:"options"`
Local bool `json:"local" mapstructure:"local"`
Path string `json:"path" mapstructure:"path"`
}

View File

@ -1,6 +1,8 @@
package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -8,35 +10,27 @@ import (
func (c *Sys) ListAuth() (map[string]*AuthMount, error) {
r := c.c.NewRequest("GET", "/v1/sys/auth")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
mounts := map[string]*AuthMount{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res AuthMount
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
return mounts, nil
@ -56,7 +50,9 @@ func (c *Sys) EnableAuthWithOptions(path string, options *EnableAuthOptions) err
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -67,53 +63,18 @@ func (c *Sys) EnableAuthWithOptions(path string, options *EnableAuthOptions) err
func (c *Sys) DisableAuth(path string) error {
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/auth/%s", path))
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
return err
}
// Structures for the requests/resposne are all down here. They aren't
// individually documented because the map almost directly to the raw HTTP API
// documentation. Please refer to that documentation for more details.
type EnableAuthOptions struct {
Type string `json:"type"`
Description string `json:"description"`
Config AuthConfigInput `json:"config"`
Local bool `json:"local"`
PluginName string `json:"plugin_name,omitempty"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
Options map[string]string `json:"options" mapstructure:"options"`
}
type AuthConfigInput struct {
DefaultLeaseTTL string `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
MaxLeaseTTL string `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
}
type AuthMount struct {
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Accessor string `json:"accessor" mapstructure:"accessor"`
Config AuthConfigOutput `json:"config" mapstructure:"config"`
Local bool `json:"local" mapstructure:"local"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
Options map[string]string `json:"options" mapstructure:"options"`
}
type AuthConfigOutput struct {
DefaultLeaseTTL int `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
MaxLeaseTTL int `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
}
// Rather than duplicate, we can use modern Go's type aliasing
type EnableAuthOptions = MountInput
type AuthConfigInput = MountConfigInput
type AuthMount = MountOutput
type AuthConfigOutput = MountConfigOutput

View File

@ -1,6 +1,12 @@
package api
import "fmt"
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) CapabilitiesSelf(path string) ([]string, error) {
return c.Capabilities(c.c.Token(), path)
@ -22,28 +28,37 @@ func (c *Sys) Capabilities(token, path string) ([]string, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var res []string
err = mapstructure.Decode(secret.Data[path], &res)
if err != nil {
return nil, err
}
if result["capabilities"] == nil {
return nil, nil
if len(res) == 0 {
_, ok := secret.Data["capabilities"]
if ok {
err = mapstructure.Decode(secret.Data["capabilities"], &res)
if err != nil {
return nil, err
}
}
}
var capabilities []string
capabilitiesRaw, ok := result["capabilities"].([]interface{})
if !ok {
return nil, fmt.Errorf("error interpreting returned capabilities")
}
for _, capability := range capabilitiesRaw {
capabilities = append(capabilities, capability.(string))
}
return capabilities, nil
return res, nil
}

View File

@ -1,15 +1,37 @@
package api
import (
"context"
"errors"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) CORSStatus() (*CORSResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/config/cors")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result CORSResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -19,38 +41,65 @@ func (c *Sys) ConfigureCORS(req *CORSRequest) (*CORSResponse, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result CORSResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
func (c *Sys) DisableCORS() (*CORSResponse, error) {
r := c.c.NewRequest("DELETE", "/v1/sys/config/cors")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result CORSResponse
err = resp.DecodeJSON(&result)
return &result, err
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result CORSResponse
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
type CORSRequest struct {
AllowedOrigins string `json:"allowed_origins"`
Enabled bool `json:"enabled"`
AllowedOrigins string `json:"allowed_origins" mapstructure:"allowed_origins"`
Enabled bool `json:"enabled" mapstructure:"enabled"`
}
type CORSResponse struct {
AllowedOrigins string `json:"allowed_origins"`
Enabled bool `json:"enabled"`
AllowedOrigins string `json:"allowed_origins" mapstructure:"allowed_origins"`
Enabled bool `json:"enabled" mapstructure:"enabled"`
}

View File

@ -1,5 +1,7 @@
package api
import "context"
func (c *Sys) GenerateRootStatus() (*GenerateRootStatusResponse, error) {
return c.generateRootStatusCommon("/v1/sys/generate-root/attempt")
}
@ -10,7 +12,10 @@ func (c *Sys) GenerateDROperationTokenStatus() (*GenerateRootStatusResponse, err
func (c *Sys) generateRootStatusCommon(path string) (*GenerateRootStatusResponse, error) {
r := c.c.NewRequest("GET", path)
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -40,7 +45,9 @@ func (c *Sys) generateRootInitCommon(path, otp, pgpKey string) (*GenerateRootSta
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -61,7 +68,10 @@ func (c *Sys) GenerateDROperationTokenCancel() error {
func (c *Sys) generateRootCancelCommon(path string) error {
r := c.c.NewRequest("DELETE", path)
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -87,7 +97,9 @@ func (c *Sys) generateRootUpdateCommon(path, shard, nonce string) (*GenerateRoot
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -107,4 +119,6 @@ type GenerateRootStatusResponse struct {
EncodedToken string `json:"encoded_token"`
EncodedRootToken string `json:"encoded_root_token"`
PGPFingerprint string `json:"pgp_fingerprint"`
OTP string `json:"otp"`
OTPLength int `json:"otp_length"`
}

View File

@ -1,5 +1,7 @@
package api
import "context"
func (c *Sys) Health() (*HealthResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/health")
// If the code is 400 or above it will automatically turn into an error,
@ -9,7 +11,11 @@ func (c *Sys) Health() (*HealthResponse, error) {
r.Params.Add("sealedcode", "299")
r.Params.Add("standbycode", "299")
r.Params.Add("drsecondarycode", "299")
resp, err := c.c.RawRequest(r)
r.Params.Add("performancestandbycode", "299")
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -24,10 +30,12 @@ type HealthResponse struct {
Initialized bool `json:"initialized"`
Sealed bool `json:"sealed"`
Standby bool `json:"standby"`
PerformanceStandby bool `json:"performance_standby"`
ReplicationPerformanceMode string `json:"replication_performance_mode"`
ReplicationDRMode string `json:"replication_dr_mode"`
ServerTimeUTC int64 `json:"server_time_utc"`
Version string `json:"version"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"`
LastWAL uint64 `json:"last_wal,omitempty"`
}

View File

@ -1,8 +1,13 @@
package api
import "context"
func (c *Sys) InitStatus() (bool, error) {
r := c.c.NewRequest("GET", "/v1/sys/init")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return false, err
}
@ -19,7 +24,9 @@ func (c *Sys) Init(opts *InitRequest) (*InitResponse, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}

View File

@ -1,8 +1,13 @@
package api
import "context"
func (c *Sys) Leader() (*LeaderResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/leader")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -14,8 +19,11 @@ func (c *Sys) Leader() (*LeaderResponse, error) {
}
type LeaderResponse struct {
HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"`
LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"`
HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"`
LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"`
PerfStandby bool `json:"performance_standby"`
PerfStandbyLastRemoteWAL uint64 `json:"performance_standby_last_remote_wal"`
LastWAL uint64 `json:"last_wal"`
}

View File

@ -1,5 +1,10 @@
package api
import (
"context"
"errors"
)
func (c *Sys) Renew(id string, increment int) (*Secret, error) {
r := c.c.NewRequest("PUT", "/v1/sys/leases/renew")
@ -11,7 +16,9 @@ func (c *Sys) Renew(id string, increment int) (*Secret, error) {
return nil, err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
@ -22,7 +29,10 @@ func (c *Sys) Renew(id string, increment int) (*Secret, error) {
func (c *Sys) Revoke(id string) error {
r := c.c.NewRequest("PUT", "/v1/sys/leases/revoke/"+id)
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -31,7 +41,10 @@ func (c *Sys) Revoke(id string) error {
func (c *Sys) RevokePrefix(id string) error {
r := c.c.NewRequest("PUT", "/v1/sys/leases/revoke-prefix/"+id)
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -40,9 +53,53 @@ func (c *Sys) RevokePrefix(id string) error {
func (c *Sys) RevokeForce(id string) error {
r := c.c.NewRequest("PUT", "/v1/sys/leases/revoke-force/"+id)
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
return err
}
func (c *Sys) RevokeWithOptions(opts *RevokeOptions) error {
if opts == nil {
return errors.New("nil options provided")
}
// Construct path
path := "/v1/sys/leases/revoke/"
switch {
case opts.Force:
path = "/v1/sys/leases/revoke-force/"
case opts.Prefix:
path = "/v1/sys/leases/revoke-prefix/"
}
path += opts.LeaseID
r := c.c.NewRequest("PUT", path)
if !opts.Force {
body := map[string]interface{}{
"sync": opts.Sync,
}
if err := r.SetJSONBody(body); err != nil {
return err
}
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
return err
}
type RevokeOptions struct {
LeaseID string
Force bool
Prefix bool
Sync bool
}

View File

@ -1,6 +1,8 @@
package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -8,35 +10,27 @@ import (
func (c *Sys) ListMounts() (map[string]*MountOutput, error) {
r := c.c.NewRequest("GET", "/v1/sys/mounts")
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
mounts := map[string]*MountOutput{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res MountOutput
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
return mounts, nil
@ -48,7 +42,9 @@ func (c *Sys) Mount(path string, mountInfo *MountInput) error {
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return err
}
@ -59,7 +55,10 @@ func (c *Sys) Mount(path string, mountInfo *MountInput) error {
func (c *Sys) Unmount(path string) error {
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/mounts/%s", path))
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -77,7 +76,9 @@ func (c *Sys) Remount(from, to string) error {
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -90,7 +91,9 @@ func (c *Sys) TuneMount(path string, config MountConfigInput) error {
return err
}
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
}
@ -100,14 +103,24 @@ func (c *Sys) TuneMount(path string, config MountConfigInput) error {
func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) {
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/mounts/%s/tune", path))
resp, err := c.c.RawRequest(r)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result MountConfigOutput
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
@ -119,25 +132,34 @@ type MountInput struct {
Type string `json:"type"`
Description string `json:"description"`
Config MountConfigInput `json:"config"`
Options map[string]string `json:"options"`
Local bool `json:"local"`
PluginName string `json:"plugin_name,omitempty"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
Options map[string]string `json:"options"`
// Deprecated: Newer server responses should be returning this information in the
// Type field (json: "type") instead.
PluginName string `json:"plugin_name,omitempty"`
}
type MountConfigInput struct {
Options map[string]string `json:"options" mapstructure:"options"`
DefaultLeaseTTL string `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
Description *string `json:"description,omitempty" mapstructure:"description"`
MaxLeaseTTL string `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
ForceNoCache bool `json:"force_no_cache" mapstructure:"force_no_cache"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
AllowedResponseHeaders []string `json:"allowed_response_headers,omitempty" mapstructure:"allowed_response_headers"`
TokenType string `json:"token_type,omitempty" mapstructure:"token_type"`
// Deprecated: This field will always be blank for newer server responses.
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
}
type MountOutput struct {
UUID string `json:"uuid"`
Type string `json:"type"`
Description string `json:"description"`
Accessor string `json:"accessor"`
@ -151,9 +173,13 @@ type MountConfigOutput struct {
DefaultLeaseTTL int `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
MaxLeaseTTL int `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
ForceNoCache bool `json:"force_no_cache" mapstructure:"force_no_cache"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
AllowedResponseHeaders []string `json:"allowed_response_headers,omitempty" mapstructure:"allowed_response_headers"`
TokenType string `json:"token_type,omitempty" mapstructure:"token_type"`
// Deprecated: This field will always be blank for newer server responses.
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
}

View File

@ -1,45 +1,136 @@
package api
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/mitchellh/mapstructure"
)
// ListPluginsInput is used as input to the ListPlugins function.
type ListPluginsInput struct{}
type ListPluginsInput struct {
// Type of the plugin. Required.
Type consts.PluginType `json:"type"`
}
// ListPluginsResponse is the response from the ListPlugins call.
type ListPluginsResponse struct {
// PluginsByType is the list of plugins by type.
PluginsByType map[consts.PluginType][]string `json:"types"`
// Names is the list of names of the plugins.
Names []string
//
// Deprecated: Newer server responses should be returning PluginsByType (json:
// "types") instead.
Names []string `json:"names"`
}
// ListPlugins lists all plugins in the catalog and returns their names as a
// list of strings.
func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
path := "/v1/sys/plugins/catalog"
req := c.c.NewRequest("LIST", path)
resp, err := c.c.RawRequest(req)
if err != nil {
path := ""
method := ""
if i.Type == consts.PluginTypeUnknown {
path = "/v1/sys/plugins/catalog"
method = "GET"
} else {
path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Type)
method = "LIST"
}
req := c.c.NewRequest(method, path)
if method == "LIST" {
// Set this for broader compatibility, but we use LIST above to be able
// to handle the wrapping lookup function
req.Method = "GET"
req.Params.Set("list", "true")
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, req)
if err != nil && resp == nil {
return nil, err
}
if resp == nil {
return nil, nil
}
defer resp.Body.Close()
var result struct {
Data struct {
Keys []string `json:"keys"`
} `json:"data"`
}
if err := resp.DecodeJSON(&result); err != nil {
return nil, err
// We received an Unsupported Operation response from Vault, indicating
// Vault of an older version that doesn't support the GET method yet;
// switch it to a LIST.
if resp.StatusCode == 405 {
req.Params.Set("list", "true")
resp, err := c.c.RawRequestWithContext(ctx, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Data struct {
Keys []string `json:"keys"`
} `json:"data"`
}
if err := resp.DecodeJSON(&result); err != nil {
return nil, err
}
return &ListPluginsResponse{Names: result.Data.Keys}, nil
}
return &ListPluginsResponse{Names: result.Data.Keys}, nil
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
result := &ListPluginsResponse{
PluginsByType: make(map[consts.PluginType][]string),
}
if i.Type == consts.PluginTypeUnknown {
for pluginTypeStr, pluginsRaw := range secret.Data {
pluginType, err := consts.ParsePluginType(pluginTypeStr)
if err != nil {
return nil, err
}
pluginsIfc, ok := pluginsRaw.([]interface{})
if !ok {
return nil, fmt.Errorf("unable to parse plugins for %q type", pluginTypeStr)
}
plugins := make([]string, len(pluginsIfc))
for i, nameIfc := range pluginsIfc {
name, ok := nameIfc.(string)
if !ok {
}
plugins[i] = name
}
result.PluginsByType[pluginType] = plugins
}
} else {
var respKeys []string
if err := mapstructure.Decode(secret.Data["keys"], &respKeys); err != nil {
return nil, err
}
result.PluginsByType[i.Type] = respKeys
}
return result, nil
}
// GetPluginInput is used as input to the GetPlugin function.
type GetPluginInput struct {
Name string `json:"-"`
// Type of the plugin. Required.
Type consts.PluginType `json:"type"`
}
// GetPluginResponse is the response from the GetPlugin call.
@ -51,21 +142,27 @@ type GetPluginResponse struct {
SHA256 string `json:"sha256"`
}
// GetPlugin retrieves information about the plugin.
func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) {
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name)
path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodGet, path)
resp, err := c.c.RawRequest(req)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result GetPluginResponse
var result struct {
Data *GetPluginResponse
}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
return &result, err
return result.Data, err
}
// RegisterPluginInput is used as input to the RegisterPlugin function.
@ -73,6 +170,9 @@ type RegisterPluginInput struct {
// Name is the name of the plugin. Required.
Name string `json:"-"`
// Type of the plugin. Required.
Type consts.PluginType `json:"type"`
// Args is the list of args to spawn the process with.
Args []string `json:"args,omitempty"`
@ -85,13 +185,16 @@ type RegisterPluginInput struct {
// RegisterPlugin registers the plugin with the given information.
func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name)
path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodPut, path)
if err := req.SetJSONBody(i); err != nil {
return err
}
resp, err := c.c.RawRequest(req)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, req)
if err == nil {
defer resp.Body.Close()
}
@ -102,16 +205,34 @@ func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
type DeregisterPluginInput struct {
// Name is the name of the plugin. Required.
Name string `json:"-"`
// Type of the plugin. Required.
Type consts.PluginType `json:"type"`
}
// DeregisterPlugin removes the plugin with the given name from the plugin
// catalog.
func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name)
path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodDelete, path)
resp, err := c.c.RawRequest(req)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, req)
if err == nil {
defer resp.Body.Close()
}
return err
}
// catalogPathByType is a helper to construct the proper API path by plugin type
func catalogPathByType(pluginType consts.PluginType, name string) string {
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", pluginType, name)
// Backwards compat, if type is not provided then use old path
if pluginType == consts.PluginTypeUnknown {
path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", name)
}
return path
}

Some files were not shown because too many files have changed in this diff Show More