mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
Fix build (#5457)
This commit is contained in:
parent
bf154cdff8
commit
b6ca59f0b4
@ -214,7 +214,7 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, t
|
||||
|
||||
var allTeams []*github.Team
|
||||
for {
|
||||
teams, resp, err := client.Organizations.ListUserTeams(ctx, teamOpt)
|
||||
teams, resp, err := client.Teams.ListUserTeams(ctx, teamOpt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@ -12,13 +12,13 @@ import (
|
||||
"time"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/clientv3/concurrency"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/physical"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/clientv3/concurrency"
|
||||
"go.etcd.io/etcd/pkg/transport"
|
||||
)
|
||||
|
||||
// EtcdBackend is a physical backend that stores data at specific
|
||||
|
||||
3
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils/utils.go
generated
vendored
3
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils/utils.go
generated
vendored
@ -20,11 +20,12 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/satori/go.uuid"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// if you use go 1.10 or higher, you can hack this util by these to avoid "TimeZone.zip not found" on Windows
|
||||
|
||||
17
vendor/go.etcd.io/etcd/clientv3/concurrency/doc.go
generated
vendored
Normal file
17
vendor/go.etcd.io/etcd/clientv3/concurrency/doc.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package concurrency implements concurrency operations on top of
|
||||
// etcd such as distributed locks, barriers, and elections.
|
||||
package concurrency
|
||||
253
vendor/go.etcd.io/etcd/clientv3/concurrency/election.go
generated
vendored
Normal file
253
vendor/go.etcd.io/etcd/clientv3/concurrency/election.go
generated
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
v3 "go.etcd.io/etcd/clientv3"
|
||||
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrElectionNotLeader = errors.New("election: not leader")
|
||||
ErrElectionNoLeader = errors.New("election: no leader")
|
||||
)
|
||||
|
||||
type Election struct {
|
||||
session *Session
|
||||
|
||||
keyPrefix string
|
||||
|
||||
leaderKey string
|
||||
leaderRev int64
|
||||
leaderSession *Session
|
||||
hdr *pb.ResponseHeader
|
||||
}
|
||||
|
||||
// NewElection returns a new election on a given key prefix.
|
||||
func NewElection(s *Session, pfx string) *Election {
|
||||
return &Election{session: s, keyPrefix: pfx + "/"}
|
||||
}
|
||||
|
||||
// ResumeElection initializes an election with a known leader.
|
||||
func ResumeElection(s *Session, pfx string, leaderKey string, leaderRev int64) *Election {
|
||||
return &Election{
|
||||
session: s,
|
||||
leaderKey: leaderKey,
|
||||
leaderRev: leaderRev,
|
||||
leaderSession: s,
|
||||
}
|
||||
}
|
||||
|
||||
// Campaign puts a value as eligible for the election on the prefix
|
||||
// key.
|
||||
// Multiple sessions can participate in the election for the
|
||||
// same prefix, but only one can be the leader at a time.
|
||||
//
|
||||
// If the context is 'context.TODO()/context.Background()', the Campaign
|
||||
// will continue to be blocked for other keys to be deleted, unless server
|
||||
// returns a non-recoverable error (e.g. ErrCompacted).
|
||||
// Otherwise, until the context is not cancelled or timed-out, Campaign will
|
||||
// continue to be blocked until it becomes the leader.
|
||||
func (e *Election) Campaign(ctx context.Context, val string) error {
|
||||
s := e.session
|
||||
client := e.session.Client()
|
||||
|
||||
k := fmt.Sprintf("%s%x", e.keyPrefix, s.Lease())
|
||||
txn := client.Txn(ctx).If(v3.Compare(v3.CreateRevision(k), "=", 0))
|
||||
txn = txn.Then(v3.OpPut(k, val, v3.WithLease(s.Lease())))
|
||||
txn = txn.Else(v3.OpGet(k))
|
||||
resp, err := txn.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.leaderKey, e.leaderRev, e.leaderSession = k, resp.Header.Revision, s
|
||||
if !resp.Succeeded {
|
||||
kv := resp.Responses[0].GetResponseRange().Kvs[0]
|
||||
e.leaderRev = kv.CreateRevision
|
||||
if string(kv.Value) != val {
|
||||
if err = e.Proclaim(ctx, val); err != nil {
|
||||
e.Resign(ctx)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = waitDeletes(ctx, client, e.keyPrefix, e.leaderRev-1)
|
||||
if err != nil {
|
||||
// clean up in case of context cancel
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
e.Resign(client.Ctx())
|
||||
default:
|
||||
e.leaderSession = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
e.hdr = resp.Header
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Proclaim lets the leader announce a new value without another election.
|
||||
func (e *Election) Proclaim(ctx context.Context, val string) error {
|
||||
if e.leaderSession == nil {
|
||||
return ErrElectionNotLeader
|
||||
}
|
||||
client := e.session.Client()
|
||||
cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev)
|
||||
txn := client.Txn(ctx).If(cmp)
|
||||
txn = txn.Then(v3.OpPut(e.leaderKey, val, v3.WithLease(e.leaderSession.Lease())))
|
||||
tresp, terr := txn.Commit()
|
||||
if terr != nil {
|
||||
return terr
|
||||
}
|
||||
if !tresp.Succeeded {
|
||||
e.leaderKey = ""
|
||||
return ErrElectionNotLeader
|
||||
}
|
||||
|
||||
e.hdr = tresp.Header
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resign lets a leader start a new election.
|
||||
func (e *Election) Resign(ctx context.Context) (err error) {
|
||||
if e.leaderSession == nil {
|
||||
return nil
|
||||
}
|
||||
client := e.session.Client()
|
||||
cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev)
|
||||
resp, err := client.Txn(ctx).If(cmp).Then(v3.OpDelete(e.leaderKey)).Commit()
|
||||
if err == nil {
|
||||
e.hdr = resp.Header
|
||||
}
|
||||
e.leaderKey = ""
|
||||
e.leaderSession = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Leader returns the leader value for the current election.
|
||||
func (e *Election) Leader(ctx context.Context) (*v3.GetResponse, error) {
|
||||
client := e.session.Client()
|
||||
resp, err := client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(resp.Kvs) == 0 {
|
||||
// no leader currently elected
|
||||
return nil, ErrElectionNoLeader
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Observe returns a channel that reliably observes ordered leader proposals
|
||||
// as GetResponse values on every current elected leader key. It will not
|
||||
// necessarily fetch all historical leader updates, but will always post the
|
||||
// most recent leader value.
|
||||
//
|
||||
// The channel closes when the context is canceled or the underlying watcher
|
||||
// is otherwise disrupted.
|
||||
func (e *Election) Observe(ctx context.Context) <-chan v3.GetResponse {
|
||||
retc := make(chan v3.GetResponse)
|
||||
go e.observe(ctx, retc)
|
||||
return retc
|
||||
}
|
||||
|
||||
func (e *Election) observe(ctx context.Context, ch chan<- v3.GetResponse) {
|
||||
client := e.session.Client()
|
||||
|
||||
defer close(ch)
|
||||
for {
|
||||
resp, err := client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var kv *mvccpb.KeyValue
|
||||
var hdr *pb.ResponseHeader
|
||||
|
||||
if len(resp.Kvs) == 0 {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
// wait for first key put on prefix
|
||||
opts := []v3.OpOption{v3.WithRev(resp.Header.Revision), v3.WithPrefix()}
|
||||
wch := client.Watch(cctx, e.keyPrefix, opts...)
|
||||
for kv == nil {
|
||||
wr, ok := <-wch
|
||||
if !ok || wr.Err() != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
// only accept puts; a delete will make observe() spin
|
||||
for _, ev := range wr.Events {
|
||||
if ev.Type == mvccpb.PUT {
|
||||
hdr, kv = &wr.Header, ev.Kv
|
||||
// may have multiple revs; hdr.rev = the last rev
|
||||
// set to kv's rev in case batch has multiple Puts
|
||||
hdr.Revision = kv.ModRevision
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
} else {
|
||||
hdr, kv = resp.Header, resp.Kvs[0]
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- v3.GetResponse{Header: hdr, Kvs: []*mvccpb.KeyValue{kv}}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
wch := client.Watch(cctx, string(kv.Key), v3.WithRev(hdr.Revision+1))
|
||||
keyDeleted := false
|
||||
for !keyDeleted {
|
||||
wr, ok := <-wch
|
||||
if !ok {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
for _, ev := range wr.Events {
|
||||
if ev.Type == mvccpb.DELETE {
|
||||
keyDeleted = true
|
||||
break
|
||||
}
|
||||
resp.Header = &wr.Header
|
||||
resp.Kvs = []*mvccpb.KeyValue{ev.Kv}
|
||||
select {
|
||||
case ch <- *resp:
|
||||
case <-cctx.Done():
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the leader key if elected, empty string otherwise.
|
||||
func (e *Election) Key() string { return e.leaderKey }
|
||||
|
||||
// Rev returns the leader key's creation revision, if elected.
|
||||
func (e *Election) Rev() int64 { return e.leaderRev }
|
||||
|
||||
// Header is the response header from the last successful election proposal.
|
||||
func (e *Election) Header() *pb.ResponseHeader { return e.hdr }
|
||||
65
vendor/go.etcd.io/etcd/clientv3/concurrency/key.go
generated
vendored
Normal file
65
vendor/go.etcd.io/etcd/clientv3/concurrency/key.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v3 "go.etcd.io/etcd/clientv3"
|
||||
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
func waitDelete(ctx context.Context, client *v3.Client, key string, rev int64) error {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
var wr v3.WatchResponse
|
||||
wch := client.Watch(cctx, key, v3.WithRev(rev))
|
||||
for wr = range wch {
|
||||
for _, ev := range wr.Events {
|
||||
if ev.Type == mvccpb.DELETE {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := wr.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("lost watcher waiting for delete")
|
||||
}
|
||||
|
||||
// waitDeletes efficiently waits until all keys matching the prefix and no greater
|
||||
// than the create revision.
|
||||
func waitDeletes(ctx context.Context, client *v3.Client, pfx string, maxCreateRev int64) (*pb.ResponseHeader, error) {
|
||||
getOpts := append(v3.WithLastCreate(), v3.WithMaxCreateRev(maxCreateRev))
|
||||
for {
|
||||
resp, err := client.Get(ctx, pfx, getOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.Kvs) == 0 {
|
||||
return resp.Header, nil
|
||||
}
|
||||
lastKey := string(resp.Kvs[0].Key)
|
||||
if err = waitDelete(ctx, client, lastKey, resp.Header.Revision); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
118
vendor/go.etcd.io/etcd/clientv3/concurrency/mutex.go
generated
vendored
Normal file
118
vendor/go.etcd.io/etcd/clientv3/concurrency/mutex.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
v3 "go.etcd.io/etcd/clientv3"
|
||||
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
|
||||
)
|
||||
|
||||
// Mutex implements the sync Locker interface with etcd
|
||||
type Mutex struct {
|
||||
s *Session
|
||||
|
||||
pfx string
|
||||
myKey string
|
||||
myRev int64
|
||||
hdr *pb.ResponseHeader
|
||||
}
|
||||
|
||||
func NewMutex(s *Session, pfx string) *Mutex {
|
||||
return &Mutex{s, pfx + "/", "", -1, nil}
|
||||
}
|
||||
|
||||
// Lock locks the mutex with a cancelable context. If the context is canceled
|
||||
// while trying to acquire the lock, the mutex tries to clean its stale lock entry.
|
||||
func (m *Mutex) Lock(ctx context.Context) error {
|
||||
s := m.s
|
||||
client := m.s.Client()
|
||||
|
||||
m.myKey = fmt.Sprintf("%s%x", m.pfx, s.Lease())
|
||||
cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0)
|
||||
// put self in lock waiters via myKey; oldest waiter holds lock
|
||||
put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease()))
|
||||
// reuse key in case this session already holds the lock
|
||||
get := v3.OpGet(m.myKey)
|
||||
// fetch current holder to complete uncontended path with only one RPC
|
||||
getOwner := v3.OpGet(m.pfx, v3.WithFirstCreate()...)
|
||||
resp, err := client.Txn(ctx).If(cmp).Then(put, getOwner).Else(get, getOwner).Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.myRev = resp.Header.Revision
|
||||
if !resp.Succeeded {
|
||||
m.myRev = resp.Responses[0].GetResponseRange().Kvs[0].CreateRevision
|
||||
}
|
||||
// if no key on prefix / the minimum rev is key, already hold the lock
|
||||
ownerKey := resp.Responses[1].GetResponseRange().Kvs
|
||||
if len(ownerKey) == 0 || ownerKey[0].CreateRevision == m.myRev {
|
||||
m.hdr = resp.Header
|
||||
return nil
|
||||
}
|
||||
|
||||
// wait for deletion revisions prior to myKey
|
||||
hdr, werr := waitDeletes(ctx, client, m.pfx, m.myRev-1)
|
||||
// release lock key if cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.Unlock(client.Ctx())
|
||||
default:
|
||||
m.hdr = hdr
|
||||
}
|
||||
return werr
|
||||
}
|
||||
|
||||
func (m *Mutex) Unlock(ctx context.Context) error {
|
||||
client := m.s.Client()
|
||||
if _, err := client.Delete(ctx, m.myKey); err != nil {
|
||||
return err
|
||||
}
|
||||
m.myKey = "\x00"
|
||||
m.myRev = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mutex) IsOwner() v3.Cmp {
|
||||
return v3.Compare(v3.CreateRevision(m.myKey), "=", m.myRev)
|
||||
}
|
||||
|
||||
func (m *Mutex) Key() string { return m.myKey }
|
||||
|
||||
// Header is the response header received from etcd on acquiring the lock.
|
||||
func (m *Mutex) Header() *pb.ResponseHeader { return m.hdr }
|
||||
|
||||
type lockerMutex struct{ *Mutex }
|
||||
|
||||
func (lm *lockerMutex) Lock() {
|
||||
client := lm.s.Client()
|
||||
if err := lm.Mutex.Lock(client.Ctx()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
func (lm *lockerMutex) Unlock() {
|
||||
client := lm.s.Client()
|
||||
if err := lm.Mutex.Unlock(client.Ctx()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocker creates a sync.Locker backed by an etcd mutex.
|
||||
func NewLocker(s *Session, pfx string) sync.Locker {
|
||||
return &lockerMutex{NewMutex(s, pfx)}
|
||||
}
|
||||
141
vendor/go.etcd.io/etcd/clientv3/concurrency/session.go
generated
vendored
Normal file
141
vendor/go.etcd.io/etcd/clientv3/concurrency/session.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
v3 "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
const defaultSessionTTL = 60
|
||||
|
||||
// Session represents a lease kept alive for the lifetime of a client.
|
||||
// Fault-tolerant applications may use sessions to reason about liveness.
|
||||
type Session struct {
|
||||
client *v3.Client
|
||||
opts *sessionOptions
|
||||
id v3.LeaseID
|
||||
|
||||
cancel context.CancelFunc
|
||||
donec <-chan struct{}
|
||||
}
|
||||
|
||||
// NewSession gets the leased session for a client.
|
||||
func NewSession(client *v3.Client, opts ...SessionOption) (*Session, error) {
|
||||
ops := &sessionOptions{ttl: defaultSessionTTL, ctx: client.Ctx()}
|
||||
for _, opt := range opts {
|
||||
opt(ops)
|
||||
}
|
||||
|
||||
id := ops.leaseID
|
||||
if id == v3.NoLease {
|
||||
resp, err := client.Grant(ops.ctx, int64(ops.ttl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = v3.LeaseID(resp.ID)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ops.ctx)
|
||||
keepAlive, err := client.KeepAlive(ctx, id)
|
||||
if err != nil || keepAlive == nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
donec := make(chan struct{})
|
||||
s := &Session{client: client, opts: ops, id: id, cancel: cancel, donec: donec}
|
||||
|
||||
// keep the lease alive until client error or cancelled context
|
||||
go func() {
|
||||
defer close(donec)
|
||||
for range keepAlive {
|
||||
// eat messages until keep alive channel closes
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Client is the etcd client that is attached to the session.
|
||||
func (s *Session) Client() *v3.Client {
|
||||
return s.client
|
||||
}
|
||||
|
||||
// Lease is the lease ID for keys bound to the session.
|
||||
func (s *Session) Lease() v3.LeaseID { return s.id }
|
||||
|
||||
// Done returns a channel that closes when the lease is orphaned, expires, or
|
||||
// is otherwise no longer being refreshed.
|
||||
func (s *Session) Done() <-chan struct{} { return s.donec }
|
||||
|
||||
// Orphan ends the refresh for the session lease. This is useful
|
||||
// in case the state of the client connection is indeterminate (revoke
|
||||
// would fail) or when transferring lease ownership.
|
||||
func (s *Session) Orphan() {
|
||||
s.cancel()
|
||||
<-s.donec
|
||||
}
|
||||
|
||||
// Close orphans the session and revokes the session lease.
|
||||
func (s *Session) Close() error {
|
||||
s.Orphan()
|
||||
// if revoke takes longer than the ttl, lease is expired anyway
|
||||
ctx, cancel := context.WithTimeout(s.opts.ctx, time.Duration(s.opts.ttl)*time.Second)
|
||||
_, err := s.client.Revoke(ctx, s.id)
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
type sessionOptions struct {
|
||||
ttl int
|
||||
leaseID v3.LeaseID
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// SessionOption configures Session.
|
||||
type SessionOption func(*sessionOptions)
|
||||
|
||||
// WithTTL configures the session's TTL in seconds.
|
||||
// If TTL is <= 0, the default 60 seconds TTL will be used.
|
||||
func WithTTL(ttl int) SessionOption {
|
||||
return func(so *sessionOptions) {
|
||||
if ttl > 0 {
|
||||
so.ttl = ttl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithLease specifies the existing leaseID to be used for the session.
|
||||
// This is useful in process restart scenario, for example, to reclaim
|
||||
// leadership from an election prior to restart.
|
||||
func WithLease(leaseID v3.LeaseID) SessionOption {
|
||||
return func(so *sessionOptions) {
|
||||
so.leaseID = leaseID
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext assigns a context to the session instead of defaulting to
|
||||
// using the client context. This is useful for canceling NewSession and
|
||||
// Close operations immediately without having to close the client. If the
|
||||
// context is canceled before Close() completes, the session's lease will be
|
||||
// abandoned and left to expire instead of being revoked.
|
||||
func WithContext(ctx context.Context) SessionOption {
|
||||
return func(so *sessionOptions) {
|
||||
so.ctx = ctx
|
||||
}
|
||||
}
|
||||
387
vendor/go.etcd.io/etcd/clientv3/concurrency/stm.go
generated
vendored
Normal file
387
vendor/go.etcd.io/etcd/clientv3/concurrency/stm.go
generated
vendored
Normal file
@ -0,0 +1,387 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
v3 "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
// STM is an interface for software transactional memory.
|
||||
type STM interface {
|
||||
// Get returns the value for a key and inserts the key in the txn's read set.
|
||||
// If Get fails, it aborts the transaction with an error, never returning.
|
||||
Get(key ...string) string
|
||||
// Put adds a value for a key to the write set.
|
||||
Put(key, val string, opts ...v3.OpOption)
|
||||
// Rev returns the revision of a key in the read set.
|
||||
Rev(key string) int64
|
||||
// Del deletes a key.
|
||||
Del(key string)
|
||||
|
||||
// commit attempts to apply the txn's changes to the server.
|
||||
commit() *v3.TxnResponse
|
||||
reset()
|
||||
}
|
||||
|
||||
// Isolation is an enumeration of transactional isolation levels which
|
||||
// describes how transactions should interfere and conflict.
|
||||
type Isolation int
|
||||
|
||||
const (
|
||||
// SerializableSnapshot provides serializable isolation and also checks
|
||||
// for write conflicts.
|
||||
SerializableSnapshot Isolation = iota
|
||||
// Serializable reads within the same transaction attempt return data
|
||||
// from the at the revision of the first read.
|
||||
Serializable
|
||||
// RepeatableReads reads within the same transaction attempt always
|
||||
// return the same data.
|
||||
RepeatableReads
|
||||
// ReadCommitted reads keys from any committed revision.
|
||||
ReadCommitted
|
||||
)
|
||||
|
||||
// stmError safely passes STM errors through panic to the STM error channel.
|
||||
type stmError struct{ err error }
|
||||
|
||||
type stmOptions struct {
|
||||
iso Isolation
|
||||
ctx context.Context
|
||||
prefetch []string
|
||||
}
|
||||
|
||||
type stmOption func(*stmOptions)
|
||||
|
||||
// WithIsolation specifies the transaction isolation level.
|
||||
func WithIsolation(lvl Isolation) stmOption {
|
||||
return func(so *stmOptions) { so.iso = lvl }
|
||||
}
|
||||
|
||||
// WithAbortContext specifies the context for permanently aborting the transaction.
|
||||
func WithAbortContext(ctx context.Context) stmOption {
|
||||
return func(so *stmOptions) { so.ctx = ctx }
|
||||
}
|
||||
|
||||
// WithPrefetch is a hint to prefetch a list of keys before trying to apply.
|
||||
// If an STM transaction will unconditionally fetch a set of keys, prefetching
|
||||
// those keys will save the round-trip cost from requesting each key one by one
|
||||
// with Get().
|
||||
func WithPrefetch(keys ...string) stmOption {
|
||||
return func(so *stmOptions) { so.prefetch = append(so.prefetch, keys...) }
|
||||
}
|
||||
|
||||
// NewSTM initiates a new STM instance, using serializable snapshot isolation by default.
|
||||
func NewSTM(c *v3.Client, apply func(STM) error, so ...stmOption) (*v3.TxnResponse, error) {
|
||||
opts := &stmOptions{ctx: c.Ctx()}
|
||||
for _, f := range so {
|
||||
f(opts)
|
||||
}
|
||||
if len(opts.prefetch) != 0 {
|
||||
f := apply
|
||||
apply = func(s STM) error {
|
||||
s.Get(opts.prefetch...)
|
||||
return f(s)
|
||||
}
|
||||
}
|
||||
return runSTM(mkSTM(c, opts), apply)
|
||||
}
|
||||
|
||||
func mkSTM(c *v3.Client, opts *stmOptions) STM {
|
||||
switch opts.iso {
|
||||
case SerializableSnapshot:
|
||||
s := &stmSerializable{
|
||||
stm: stm{client: c, ctx: opts.ctx},
|
||||
prefetch: make(map[string]*v3.GetResponse),
|
||||
}
|
||||
s.conflicts = func() []v3.Cmp {
|
||||
return append(s.rset.cmps(), s.wset.cmps(s.rset.first()+1)...)
|
||||
}
|
||||
return s
|
||||
case Serializable:
|
||||
s := &stmSerializable{
|
||||
stm: stm{client: c, ctx: opts.ctx},
|
||||
prefetch: make(map[string]*v3.GetResponse),
|
||||
}
|
||||
s.conflicts = func() []v3.Cmp { return s.rset.cmps() }
|
||||
return s
|
||||
case RepeatableReads:
|
||||
s := &stm{client: c, ctx: opts.ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}
|
||||
s.conflicts = func() []v3.Cmp { return s.rset.cmps() }
|
||||
return s
|
||||
case ReadCommitted:
|
||||
s := &stm{client: c, ctx: opts.ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}
|
||||
s.conflicts = func() []v3.Cmp { return nil }
|
||||
return s
|
||||
default:
|
||||
panic("unsupported stm")
|
||||
}
|
||||
}
|
||||
|
||||
type stmResponse struct {
|
||||
resp *v3.TxnResponse
|
||||
err error
|
||||
}
|
||||
|
||||
func runSTM(s STM, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
outc := make(chan stmResponse, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
e, ok := r.(stmError)
|
||||
if !ok {
|
||||
// client apply panicked
|
||||
panic(r)
|
||||
}
|
||||
outc <- stmResponse{nil, e.err}
|
||||
}
|
||||
}()
|
||||
var out stmResponse
|
||||
for {
|
||||
s.reset()
|
||||
if out.err = apply(s); out.err != nil {
|
||||
break
|
||||
}
|
||||
if out.resp = s.commit(); out.resp != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
outc <- out
|
||||
}()
|
||||
r := <-outc
|
||||
return r.resp, r.err
|
||||
}
|
||||
|
||||
// stm implements repeatable-read software transactional memory over etcd
|
||||
type stm struct {
|
||||
client *v3.Client
|
||||
ctx context.Context
|
||||
// rset holds read key values and revisions
|
||||
rset readSet
|
||||
// wset holds overwritten keys and their values
|
||||
wset writeSet
|
||||
// getOpts are the opts used for gets
|
||||
getOpts []v3.OpOption
|
||||
// conflicts computes the current conflicts on the txn
|
||||
conflicts func() []v3.Cmp
|
||||
}
|
||||
|
||||
type stmPut struct {
|
||||
val string
|
||||
op v3.Op
|
||||
}
|
||||
|
||||
type readSet map[string]*v3.GetResponse
|
||||
|
||||
func (rs readSet) add(keys []string, txnresp *v3.TxnResponse) {
|
||||
for i, resp := range txnresp.Responses {
|
||||
rs[keys[i]] = (*v3.GetResponse)(resp.GetResponseRange())
|
||||
}
|
||||
}
|
||||
|
||||
// first returns the store revision from the first fetch
|
||||
func (rs readSet) first() int64 {
|
||||
ret := int64(math.MaxInt64 - 1)
|
||||
for _, resp := range rs {
|
||||
if rev := resp.Header.Revision; rev < ret {
|
||||
ret = rev
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// cmps guards the txn from updates to read set
|
||||
func (rs readSet) cmps() []v3.Cmp {
|
||||
cmps := make([]v3.Cmp, 0, len(rs))
|
||||
for k, rk := range rs {
|
||||
cmps = append(cmps, isKeyCurrent(k, rk))
|
||||
}
|
||||
return cmps
|
||||
}
|
||||
|
||||
type writeSet map[string]stmPut
|
||||
|
||||
func (ws writeSet) get(keys ...string) *stmPut {
|
||||
for _, key := range keys {
|
||||
if wv, ok := ws[key]; ok {
|
||||
return &wv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmps returns a cmp list testing no writes have happened past rev
|
||||
func (ws writeSet) cmps(rev int64) []v3.Cmp {
|
||||
cmps := make([]v3.Cmp, 0, len(ws))
|
||||
for key := range ws {
|
||||
cmps = append(cmps, v3.Compare(v3.ModRevision(key), "<", rev))
|
||||
}
|
||||
return cmps
|
||||
}
|
||||
|
||||
// puts is the list of ops for all pending writes
|
||||
func (ws writeSet) puts() []v3.Op {
|
||||
puts := make([]v3.Op, 0, len(ws))
|
||||
for _, v := range ws {
|
||||
puts = append(puts, v.op)
|
||||
}
|
||||
return puts
|
||||
}
|
||||
|
||||
func (s *stm) Get(keys ...string) string {
|
||||
if wv := s.wset.get(keys...); wv != nil {
|
||||
return wv.val
|
||||
}
|
||||
return respToValue(s.fetch(keys...))
|
||||
}
|
||||
|
||||
func (s *stm) Put(key, val string, opts ...v3.OpOption) {
|
||||
s.wset[key] = stmPut{val, v3.OpPut(key, val, opts...)}
|
||||
}
|
||||
|
||||
func (s *stm) Del(key string) { s.wset[key] = stmPut{"", v3.OpDelete(key)} }
|
||||
|
||||
func (s *stm) Rev(key string) int64 {
|
||||
if resp := s.fetch(key); resp != nil && len(resp.Kvs) != 0 {
|
||||
return resp.Kvs[0].ModRevision
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *stm) commit() *v3.TxnResponse {
|
||||
txnresp, err := s.client.Txn(s.ctx).If(s.conflicts()...).Then(s.wset.puts()...).Commit()
|
||||
if err != nil {
|
||||
panic(stmError{err})
|
||||
}
|
||||
if txnresp.Succeeded {
|
||||
return txnresp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stm) fetch(keys ...string) *v3.GetResponse {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
ops := make([]v3.Op, len(keys))
|
||||
for i, key := range keys {
|
||||
if resp, ok := s.rset[key]; ok {
|
||||
return resp
|
||||
}
|
||||
ops[i] = v3.OpGet(key, s.getOpts...)
|
||||
}
|
||||
txnresp, err := s.client.Txn(s.ctx).Then(ops...).Commit()
|
||||
if err != nil {
|
||||
panic(stmError{err})
|
||||
}
|
||||
s.rset.add(keys, txnresp)
|
||||
return (*v3.GetResponse)(txnresp.Responses[0].GetResponseRange())
|
||||
}
|
||||
|
||||
func (s *stm) reset() {
|
||||
s.rset = make(map[string]*v3.GetResponse)
|
||||
s.wset = make(map[string]stmPut)
|
||||
}
|
||||
|
||||
type stmSerializable struct {
|
||||
stm
|
||||
prefetch map[string]*v3.GetResponse
|
||||
}
|
||||
|
||||
func (s *stmSerializable) Get(keys ...string) string {
|
||||
if wv := s.wset.get(keys...); wv != nil {
|
||||
return wv.val
|
||||
}
|
||||
firstRead := len(s.rset) == 0
|
||||
for _, key := range keys {
|
||||
if resp, ok := s.prefetch[key]; ok {
|
||||
delete(s.prefetch, key)
|
||||
s.rset[key] = resp
|
||||
}
|
||||
}
|
||||
resp := s.stm.fetch(keys...)
|
||||
if firstRead {
|
||||
// txn's base revision is defined by the first read
|
||||
s.getOpts = []v3.OpOption{
|
||||
v3.WithRev(resp.Header.Revision),
|
||||
v3.WithSerializable(),
|
||||
}
|
||||
}
|
||||
return respToValue(resp)
|
||||
}
|
||||
|
||||
func (s *stmSerializable) Rev(key string) int64 {
|
||||
s.Get(key)
|
||||
return s.stm.Rev(key)
|
||||
}
|
||||
|
||||
func (s *stmSerializable) gets() ([]string, []v3.Op) {
|
||||
keys := make([]string, 0, len(s.rset))
|
||||
ops := make([]v3.Op, 0, len(s.rset))
|
||||
for k := range s.rset {
|
||||
keys = append(keys, k)
|
||||
ops = append(ops, v3.OpGet(k))
|
||||
}
|
||||
return keys, ops
|
||||
}
|
||||
|
||||
func (s *stmSerializable) commit() *v3.TxnResponse {
|
||||
keys, getops := s.gets()
|
||||
txn := s.client.Txn(s.ctx).If(s.conflicts()...).Then(s.wset.puts()...)
|
||||
// use Else to prefetch keys in case of conflict to save a round trip
|
||||
txnresp, err := txn.Else(getops...).Commit()
|
||||
if err != nil {
|
||||
panic(stmError{err})
|
||||
}
|
||||
if txnresp.Succeeded {
|
||||
return txnresp
|
||||
}
|
||||
// load prefetch with Else data
|
||||
s.rset.add(keys, txnresp)
|
||||
s.prefetch = s.rset
|
||||
s.getOpts = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func isKeyCurrent(k string, r *v3.GetResponse) v3.Cmp {
|
||||
if len(r.Kvs) != 0 {
|
||||
return v3.Compare(v3.ModRevision(k), "=", r.Kvs[0].ModRevision)
|
||||
}
|
||||
return v3.Compare(v3.ModRevision(k), "=", 0)
|
||||
}
|
||||
|
||||
func respToValue(resp *v3.GetResponse) string {
|
||||
if resp == nil || len(resp.Kvs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(resp.Kvs[0].Value)
|
||||
}
|
||||
|
||||
// NewSTMRepeatable is deprecated.
|
||||
func NewSTMRepeatable(ctx context.Context, c *v3.Client, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
return NewSTM(c, apply, WithAbortContext(ctx), WithIsolation(RepeatableReads))
|
||||
}
|
||||
|
||||
// NewSTMSerializable is deprecated.
|
||||
func NewSTMSerializable(ctx context.Context, c *v3.Client, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
return NewSTM(c, apply, WithAbortContext(ctx), WithIsolation(Serializable))
|
||||
}
|
||||
|
||||
// NewSTMReadCommitted is deprecated.
|
||||
func NewSTMReadCommitted(ctx context.Context, c *v3.Client, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
return NewSTM(c, apply, WithAbortContext(ctx), WithIsolation(ReadCommitted))
|
||||
}
|
||||
17
vendor/go.etcd.io/etcd/pkg/transport/doc.go
generated
vendored
Normal file
17
vendor/go.etcd.io/etcd/pkg/transport/doc.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package transport implements various HTTP transport utilities based on Go
|
||||
// net package.
|
||||
package transport
|
||||
94
vendor/go.etcd.io/etcd/pkg/transport/keepalive_listener.go
generated
vendored
Normal file
94
vendor/go.etcd.io/etcd/pkg/transport/keepalive_listener.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type keepAliveConn interface {
|
||||
SetKeepAlive(bool) error
|
||||
SetKeepAlivePeriod(d time.Duration) error
|
||||
}
|
||||
|
||||
// NewKeepAliveListener returns a listener that listens on the given address.
|
||||
// Be careful when wrap around KeepAliveListener with another Listener if TLSInfo is not nil.
|
||||
// Some pkgs (like go/http) might expect Listener to return TLSConn type to start TLS handshake.
|
||||
// http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
|
||||
func NewKeepAliveListener(l net.Listener, scheme string, tlscfg *tls.Config) (net.Listener, error) {
|
||||
if scheme == "https" {
|
||||
if tlscfg == nil {
|
||||
return nil, fmt.Errorf("cannot listen on TLS for given listener: KeyFile and CertFile are not presented")
|
||||
}
|
||||
return newTLSKeepaliveListener(l, tlscfg), nil
|
||||
}
|
||||
|
||||
return &keepaliveListener{
|
||||
Listener: l,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type keepaliveListener struct{ net.Listener }
|
||||
|
||||
func (kln *keepaliveListener) Accept() (net.Conn, error) {
|
||||
c, err := kln.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kac := c.(keepAliveConn)
|
||||
// detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl
|
||||
// default on linux: 30 + 8 * 30
|
||||
// default on osx: 30 + 8 * 75
|
||||
kac.SetKeepAlive(true)
|
||||
kac.SetKeepAlivePeriod(30 * time.Second)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// A tlsKeepaliveListener implements a network listener (net.Listener) for TLS connections.
|
||||
type tlsKeepaliveListener struct {
|
||||
net.Listener
|
||||
config *tls.Config
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next incoming TLS connection.
|
||||
// The returned connection c is a *tls.Conn.
|
||||
func (l *tlsKeepaliveListener) Accept() (c net.Conn, err error) {
|
||||
c, err = l.Listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kac := c.(keepAliveConn)
|
||||
// detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl
|
||||
// default on linux: 30 + 8 * 30
|
||||
// default on osx: 30 + 8 * 75
|
||||
kac.SetKeepAlive(true)
|
||||
kac.SetKeepAlivePeriod(30 * time.Second)
|
||||
c = tls.Server(c, l.config)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewListener creates a Listener which accepts connections from an inner
|
||||
// Listener and wraps each connection with Server.
|
||||
// The configuration config must be non-nil and must have
|
||||
// at least one certificate.
|
||||
func newTLSKeepaliveListener(inner net.Listener, config *tls.Config) net.Listener {
|
||||
l := &tlsKeepaliveListener{}
|
||||
l.Listener = inner
|
||||
l.config = config
|
||||
return l
|
||||
}
|
||||
80
vendor/go.etcd.io/etcd/pkg/transport/limit_listen.go
generated
vendored
Normal file
80
vendor/go.etcd.io/etcd/pkg/transport/limit_listen.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2013 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package transport provides network utility functions, complementing the more
|
||||
// common ones in the net package.
|
||||
package transport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotTCP = errors.New("only tcp connections have keepalive")
|
||||
)
|
||||
|
||||
// LimitListener returns a Listener that accepts at most n simultaneous
|
||||
// connections from the provided Listener.
|
||||
func LimitListener(l net.Listener, n int) net.Listener {
|
||||
return &limitListener{l, make(chan struct{}, n)}
|
||||
}
|
||||
|
||||
type limitListener struct {
|
||||
net.Listener
|
||||
sem chan struct{}
|
||||
}
|
||||
|
||||
func (l *limitListener) acquire() { l.sem <- struct{}{} }
|
||||
func (l *limitListener) release() { <-l.sem }
|
||||
|
||||
func (l *limitListener) Accept() (net.Conn, error) {
|
||||
l.acquire()
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
l.release()
|
||||
return nil, err
|
||||
}
|
||||
return &limitListenerConn{Conn: c, release: l.release}, nil
|
||||
}
|
||||
|
||||
type limitListenerConn struct {
|
||||
net.Conn
|
||||
releaseOnce sync.Once
|
||||
release func()
|
||||
}
|
||||
|
||||
func (l *limitListenerConn) Close() error {
|
||||
err := l.Conn.Close()
|
||||
l.releaseOnce.Do(l.release)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *limitListenerConn) SetKeepAlive(doKeepAlive bool) error {
|
||||
tcpc, ok := l.Conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return ErrNotTCP
|
||||
}
|
||||
return tcpc.SetKeepAlive(doKeepAlive)
|
||||
}
|
||||
|
||||
func (l *limitListenerConn) SetKeepAlivePeriod(d time.Duration) error {
|
||||
tcpc, ok := l.Conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return ErrNotTCP
|
||||
}
|
||||
return tcpc.SetKeepAlivePeriod(d)
|
||||
}
|
||||
391
vendor/go.etcd.io/etcd/pkg/transport/listener.go
generated
vendored
Normal file
391
vendor/go.etcd.io/etcd/pkg/transport/listener.go
generated
vendored
Normal file
@ -0,0 +1,391 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/pkg/tlsutil"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NewListener creates a new listner.
|
||||
func NewListener(addr, scheme string, tlsinfo *TLSInfo) (l net.Listener, err error) {
|
||||
if l, err = newListener(addr, scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapTLS(scheme, tlsinfo, l)
|
||||
}
|
||||
|
||||
func newListener(addr string, scheme string) (net.Listener, error) {
|
||||
if scheme == "unix" || scheme == "unixs" {
|
||||
// unix sockets via unix://laddr
|
||||
return NewUnixListener(addr)
|
||||
}
|
||||
return net.Listen("tcp", addr)
|
||||
}
|
||||
|
||||
func wrapTLS(scheme string, tlsinfo *TLSInfo, l net.Listener) (net.Listener, error) {
|
||||
if scheme != "https" && scheme != "unixs" {
|
||||
return l, nil
|
||||
}
|
||||
return newTLSListener(l, tlsinfo, checkSAN)
|
||||
}
|
||||
|
||||
type TLSInfo struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
TrustedCAFile string
|
||||
ClientCertAuth bool
|
||||
CRLFile string
|
||||
InsecureSkipVerify bool
|
||||
|
||||
// ServerName ensures the cert matches the given host in case of discovery / virtual hosting
|
||||
ServerName string
|
||||
|
||||
// HandshakeFailure is optionally called when a connection fails to handshake. The
|
||||
// connection will be closed immediately afterwards.
|
||||
HandshakeFailure func(*tls.Conn, error)
|
||||
|
||||
// CipherSuites is a list of supported cipher suites.
|
||||
// If empty, Go auto-populates it by default.
|
||||
// Note that cipher suites are prioritized in the given order.
|
||||
CipherSuites []uint16
|
||||
|
||||
selfCert bool
|
||||
|
||||
// parseFunc exists to simplify testing. Typically, parseFunc
|
||||
// should be left nil. In that case, tls.X509KeyPair will be used.
|
||||
parseFunc func([]byte, []byte) (tls.Certificate, error)
|
||||
|
||||
// AllowedCN is a CN which must be provided by a client.
|
||||
AllowedCN string
|
||||
|
||||
// Logger logs TLS errors.
|
||||
// If nil, all logs are discarded.
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func (info TLSInfo) String() string {
|
||||
return fmt.Sprintf("cert = %s, key = %s, trusted-ca = %s, client-cert-auth = %v, crl-file = %s", info.CertFile, info.KeyFile, info.TrustedCAFile, info.ClientCertAuth, info.CRLFile)
|
||||
}
|
||||
|
||||
func (info TLSInfo) Empty() bool {
|
||||
return info.CertFile == "" && info.KeyFile == ""
|
||||
}
|
||||
|
||||
func SelfCert(lg *zap.Logger, dirpath string, hosts []string) (info TLSInfo, err error) {
|
||||
if err = os.MkdirAll(dirpath, 0700); err != nil {
|
||||
return
|
||||
}
|
||||
info.Logger = lg
|
||||
|
||||
certPath := filepath.Join(dirpath, "cert.pem")
|
||||
keyPath := filepath.Join(dirpath, "key.pem")
|
||||
_, errcert := os.Stat(certPath)
|
||||
_, errkey := os.Stat(keyPath)
|
||||
if errcert == nil && errkey == nil {
|
||||
info.CertFile = certPath
|
||||
info.KeyFile = keyPath
|
||||
info.selfCert = true
|
||||
return
|
||||
}
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"cannot generate random number",
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{Organization: []string{"etcd"}},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * (24 * time.Hour)),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
h, _, _ := net.SplitHostPort(host)
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
|
||||
} else {
|
||||
tmpl.DNSNames = append(tmpl.DNSNames, h)
|
||||
}
|
||||
}
|
||||
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
if err != nil {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"cannot generate ECDSA key",
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"cannot generate x509 certificate",
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
certOut, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
info.Logger.Warn(
|
||||
"cannot cert file",
|
||||
zap.String("path", certPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
}
|
||||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
certOut.Close()
|
||||
if info.Logger != nil {
|
||||
info.Logger.Info("created cert file", zap.String("path", certPath))
|
||||
}
|
||||
|
||||
b, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"cannot key file",
|
||||
zap.String("path", keyPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
|
||||
keyOut.Close()
|
||||
if info.Logger != nil {
|
||||
info.Logger.Info("created key file", zap.String("path", keyPath))
|
||||
}
|
||||
return SelfCert(lg, dirpath, hosts)
|
||||
}
|
||||
|
||||
// baseConfig is called on initial TLS handshake start.
|
||||
//
|
||||
// Previously,
|
||||
// 1. Server has non-empty (*tls.Config).Certificates on client hello
|
||||
// 2. Server calls (*tls.Config).GetCertificate iff:
|
||||
// - Server's (*tls.Config).Certificates is not empty, or
|
||||
// - Client supplies SNI; non-empty (*tls.ClientHelloInfo).ServerName
|
||||
//
|
||||
// When (*tls.Config).Certificates is always populated on initial handshake,
|
||||
// client is expected to provide a valid matching SNI to pass the TLS
|
||||
// verification, thus trigger server (*tls.Config).GetCertificate to reload
|
||||
// TLS assets. However, a cert whose SAN field does not include domain names
|
||||
// but only IP addresses, has empty (*tls.ClientHelloInfo).ServerName, thus
|
||||
// it was never able to trigger TLS reload on initial handshake; first
|
||||
// ceritifcate object was being used, never being updated.
|
||||
//
|
||||
// Now, (*tls.Config).Certificates is created empty on initial TLS client
|
||||
// handshake, in order to trigger (*tls.Config).GetCertificate and populate
|
||||
// rest of the certificates on every new TLS connection, even when client
|
||||
// SNI is empty (e.g. cert only includes IPs).
|
||||
func (info TLSInfo) baseConfig() (*tls.Config, error) {
|
||||
if info.KeyFile == "" || info.CertFile == "" {
|
||||
return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile)
|
||||
}
|
||||
if info.Logger == nil {
|
||||
info.Logger = zap.NewNop()
|
||||
}
|
||||
|
||||
_, err := tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: info.ServerName,
|
||||
}
|
||||
|
||||
if len(info.CipherSuites) > 0 {
|
||||
cfg.CipherSuites = info.CipherSuites
|
||||
}
|
||||
|
||||
if info.AllowedCN != "" {
|
||||
cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
for _, chains := range verifiedChains {
|
||||
if len(chains) != 0 {
|
||||
if info.AllowedCN == chains[0].Subject.CommonName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.New("CommonName authentication failed")
|
||||
}
|
||||
}
|
||||
|
||||
// this only reloads certs when there's a client request
|
||||
// TODO: support server-side refresh (e.g. inotify, SIGHUP), caching
|
||||
cfg.GetCertificate = func(clientHello *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
|
||||
cert, err = tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
|
||||
if os.IsNotExist(err) {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"failed to find peer cert files",
|
||||
zap.String("cert-file", info.CertFile),
|
||||
zap.String("key-file", info.KeyFile),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
} else if err != nil {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"failed to create peer certificate",
|
||||
zap.String("cert-file", info.CertFile),
|
||||
zap.String("key-file", info.KeyFile),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
return cert, err
|
||||
}
|
||||
cfg.GetClientCertificate = func(unused *tls.CertificateRequestInfo) (cert *tls.Certificate, err error) {
|
||||
cert, err = tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
|
||||
if os.IsNotExist(err) {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"failed to find client cert files",
|
||||
zap.String("cert-file", info.CertFile),
|
||||
zap.String("key-file", info.KeyFile),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
} else if err != nil {
|
||||
if info.Logger != nil {
|
||||
info.Logger.Warn(
|
||||
"failed to create client certificate",
|
||||
zap.String("cert-file", info.CertFile),
|
||||
zap.String("key-file", info.KeyFile),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
return cert, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// cafiles returns a list of CA file paths.
|
||||
func (info TLSInfo) cafiles() []string {
|
||||
cs := make([]string, 0)
|
||||
if info.TrustedCAFile != "" {
|
||||
cs = append(cs, info.TrustedCAFile)
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
// ServerConfig generates a tls.Config object for use by an HTTP server.
|
||||
func (info TLSInfo) ServerConfig() (*tls.Config, error) {
|
||||
cfg, err := info.baseConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.ClientAuth = tls.NoClientCert
|
||||
if info.TrustedCAFile != "" || info.ClientCertAuth {
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
cs := info.cafiles()
|
||||
if len(cs) > 0 {
|
||||
cp, err := tlsutil.NewCertPool(cs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.ClientCAs = cp
|
||||
}
|
||||
|
||||
// "h2" NextProtos is necessary for enabling HTTP2 for go's HTTP server
|
||||
cfg.NextProtos = []string{"h2"}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// ClientConfig generates a tls.Config object for use by an HTTP client.
|
||||
func (info TLSInfo) ClientConfig() (*tls.Config, error) {
|
||||
var cfg *tls.Config
|
||||
var err error
|
||||
|
||||
if !info.Empty() {
|
||||
cfg, err = info.baseConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cfg = &tls.Config{ServerName: info.ServerName}
|
||||
}
|
||||
cfg.InsecureSkipVerify = info.InsecureSkipVerify
|
||||
|
||||
cs := info.cafiles()
|
||||
if len(cs) > 0 {
|
||||
cfg.RootCAs, err = tlsutil.NewCertPool(cs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if info.selfCert {
|
||||
cfg.InsecureSkipVerify = true
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// IsClosedConnError returns true if the error is from closing listener, cmux.
|
||||
// copied from golang.org/x/net/http2/http2.go
|
||||
func IsClosedConnError(err error) bool {
|
||||
// 'use of closed network connection' (Go <=1.8)
|
||||
// 'use of closed file or network connection' (Go >1.8, internal/poll.ErrClosing)
|
||||
// 'mux: listener closed' (cmux.ErrListenerClosed)
|
||||
return err != nil && strings.Contains(err.Error(), "closed")
|
||||
}
|
||||
272
vendor/go.etcd.io/etcd/pkg/transport/listener_tls.go
generated
vendored
Normal file
272
vendor/go.etcd.io/etcd/pkg/transport/listener_tls.go
generated
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
// Copyright 2017 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// tlsListener overrides a TLS listener so it will reject client
|
||||
// certificates with insufficient SAN credentials or CRL revoked
|
||||
// certificates.
|
||||
type tlsListener struct {
|
||||
net.Listener
|
||||
connc chan net.Conn
|
||||
donec chan struct{}
|
||||
err error
|
||||
handshakeFailure func(*tls.Conn, error)
|
||||
check tlsCheckFunc
|
||||
}
|
||||
|
||||
type tlsCheckFunc func(context.Context, *tls.Conn) error
|
||||
|
||||
// NewTLSListener handshakes TLS connections and performs optional CRL checking.
|
||||
func NewTLSListener(l net.Listener, tlsinfo *TLSInfo) (net.Listener, error) {
|
||||
check := func(context.Context, *tls.Conn) error { return nil }
|
||||
return newTLSListener(l, tlsinfo, check)
|
||||
}
|
||||
|
||||
func newTLSListener(l net.Listener, tlsinfo *TLSInfo, check tlsCheckFunc) (net.Listener, error) {
|
||||
if tlsinfo == nil || tlsinfo.Empty() {
|
||||
l.Close()
|
||||
return nil, fmt.Errorf("cannot listen on TLS for %s: KeyFile and CertFile are not presented", l.Addr().String())
|
||||
}
|
||||
tlscfg, err := tlsinfo.ServerConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hf := tlsinfo.HandshakeFailure
|
||||
if hf == nil {
|
||||
hf = func(*tls.Conn, error) {}
|
||||
}
|
||||
|
||||
if len(tlsinfo.CRLFile) > 0 {
|
||||
prevCheck := check
|
||||
check = func(ctx context.Context, tlsConn *tls.Conn) error {
|
||||
if err := prevCheck(ctx, tlsConn); err != nil {
|
||||
return err
|
||||
}
|
||||
st := tlsConn.ConnectionState()
|
||||
if certs := st.PeerCertificates; len(certs) > 0 {
|
||||
return checkCRL(tlsinfo.CRLFile, certs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
tlsl := &tlsListener{
|
||||
Listener: tls.NewListener(l, tlscfg),
|
||||
connc: make(chan net.Conn),
|
||||
donec: make(chan struct{}),
|
||||
handshakeFailure: hf,
|
||||
check: check,
|
||||
}
|
||||
go tlsl.acceptLoop()
|
||||
return tlsl, nil
|
||||
}
|
||||
|
||||
func (l *tlsListener) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-l.connc:
|
||||
return conn, nil
|
||||
case <-l.donec:
|
||||
return nil, l.err
|
||||
}
|
||||
}
|
||||
|
||||
func checkSAN(ctx context.Context, tlsConn *tls.Conn) error {
|
||||
st := tlsConn.ConnectionState()
|
||||
if certs := st.PeerCertificates; len(certs) > 0 {
|
||||
addr := tlsConn.RemoteAddr().String()
|
||||
return checkCertSAN(ctx, certs[0], addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// acceptLoop launches each TLS handshake in a separate goroutine
|
||||
// to prevent a hanging TLS connection from blocking other connections.
|
||||
func (l *tlsListener) acceptLoop() {
|
||||
var wg sync.WaitGroup
|
||||
var pendingMu sync.Mutex
|
||||
|
||||
pending := make(map[net.Conn]struct{})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer func() {
|
||||
cancel()
|
||||
pendingMu.Lock()
|
||||
for c := range pending {
|
||||
c.Close()
|
||||
}
|
||||
pendingMu.Unlock()
|
||||
wg.Wait()
|
||||
close(l.donec)
|
||||
}()
|
||||
|
||||
for {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
l.err = err
|
||||
return
|
||||
}
|
||||
|
||||
pendingMu.Lock()
|
||||
pending[conn] = struct{}{}
|
||||
pendingMu.Unlock()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
tlsConn := conn.(*tls.Conn)
|
||||
herr := tlsConn.Handshake()
|
||||
pendingMu.Lock()
|
||||
delete(pending, conn)
|
||||
pendingMu.Unlock()
|
||||
|
||||
if herr != nil {
|
||||
l.handshakeFailure(tlsConn, herr)
|
||||
return
|
||||
}
|
||||
if err := l.check(ctx, tlsConn); err != nil {
|
||||
l.handshakeFailure(tlsConn, err)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case l.connc <- tlsConn:
|
||||
conn = nil
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func checkCRL(crlPath string, cert []*x509.Certificate) error {
|
||||
// TODO: cache
|
||||
crlBytes, err := ioutil.ReadFile(crlPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certList, err := x509.ParseCRL(crlBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
revokedSerials := make(map[string]struct{})
|
||||
for _, rc := range certList.TBSCertList.RevokedCertificates {
|
||||
revokedSerials[string(rc.SerialNumber.Bytes())] = struct{}{}
|
||||
}
|
||||
for _, c := range cert {
|
||||
serial := string(c.SerialNumber.Bytes())
|
||||
if _, ok := revokedSerials[serial]; ok {
|
||||
return fmt.Errorf("transport: certificate serial %x revoked", serial)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCertSAN(ctx context.Context, cert *x509.Certificate, remoteAddr string) error {
|
||||
if len(cert.IPAddresses) == 0 && len(cert.DNSNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
h, _, herr := net.SplitHostPort(remoteAddr)
|
||||
if herr != nil {
|
||||
return herr
|
||||
}
|
||||
if len(cert.IPAddresses) > 0 {
|
||||
cerr := cert.VerifyHostname(h)
|
||||
if cerr == nil {
|
||||
return nil
|
||||
}
|
||||
if len(cert.DNSNames) == 0 {
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
if len(cert.DNSNames) > 0 {
|
||||
ok, err := isHostInDNS(ctx, h, cert.DNSNames)
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
errStr := ""
|
||||
if err != nil {
|
||||
errStr = " (" + err.Error() + ")"
|
||||
}
|
||||
return fmt.Errorf("tls: %q does not match any of DNSNames %q"+errStr, h, cert.DNSNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isHostInDNS(ctx context.Context, host string, dnsNames []string) (ok bool, err error) {
|
||||
// reverse lookup
|
||||
wildcards, names := []string{}, []string{}
|
||||
for _, dns := range dnsNames {
|
||||
if strings.HasPrefix(dns, "*.") {
|
||||
wildcards = append(wildcards, dns[1:])
|
||||
} else {
|
||||
names = append(names, dns)
|
||||
}
|
||||
}
|
||||
lnames, lerr := net.DefaultResolver.LookupAddr(ctx, host)
|
||||
for _, name := range lnames {
|
||||
// strip trailing '.' from PTR record
|
||||
if name[len(name)-1] == '.' {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
for _, wc := range wildcards {
|
||||
if strings.HasSuffix(name, wc) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
for _, n := range names {
|
||||
if n == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
err = lerr
|
||||
|
||||
// forward lookup
|
||||
for _, dns := range names {
|
||||
addrs, lerr := net.DefaultResolver.LookupHost(ctx, dns)
|
||||
if lerr != nil {
|
||||
err = lerr
|
||||
continue
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if addr == host {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (l *tlsListener) Close() error {
|
||||
err := l.Listener.Close()
|
||||
<-l.donec
|
||||
return err
|
||||
}
|
||||
44
vendor/go.etcd.io/etcd/pkg/transport/timeout_conn.go
generated
vendored
Normal file
44
vendor/go.etcd.io/etcd/pkg/transport/timeout_conn.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timeoutConn struct {
|
||||
net.Conn
|
||||
wtimeoutd time.Duration
|
||||
rdtimeoutd time.Duration
|
||||
}
|
||||
|
||||
func (c timeoutConn) Write(b []byte) (n int, err error) {
|
||||
if c.wtimeoutd > 0 {
|
||||
if err := c.SetWriteDeadline(time.Now().Add(c.wtimeoutd)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c timeoutConn) Read(b []byte) (n int, err error) {
|
||||
if c.rdtimeoutd > 0 {
|
||||
if err := c.SetReadDeadline(time.Now().Add(c.rdtimeoutd)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
36
vendor/go.etcd.io/etcd/pkg/transport/timeout_dialer.go
generated
vendored
Normal file
36
vendor/go.etcd.io/etcd/pkg/transport/timeout_dialer.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type rwTimeoutDialer struct {
|
||||
wtimeoutd time.Duration
|
||||
rdtimeoutd time.Duration
|
||||
net.Dialer
|
||||
}
|
||||
|
||||
func (d *rwTimeoutDialer) Dial(network, address string) (net.Conn, error) {
|
||||
conn, err := d.Dialer.Dial(network, address)
|
||||
tconn := &timeoutConn{
|
||||
rdtimeoutd: d.rdtimeoutd,
|
||||
wtimeoutd: d.wtimeoutd,
|
||||
Conn: conn,
|
||||
}
|
||||
return tconn, err
|
||||
}
|
||||
57
vendor/go.etcd.io/etcd/pkg/transport/timeout_listener.go
generated
vendored
Normal file
57
vendor/go.etcd.io/etcd/pkg/transport/timeout_listener.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewTimeoutListener returns a listener that listens on the given address.
|
||||
// If read/write on the accepted connection blocks longer than its time limit,
|
||||
// it will return timeout error.
|
||||
func NewTimeoutListener(addr string, scheme string, tlsinfo *TLSInfo, rdtimeoutd, wtimeoutd time.Duration) (net.Listener, error) {
|
||||
ln, err := newListener(addr, scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ln = &rwTimeoutListener{
|
||||
Listener: ln,
|
||||
rdtimeoutd: rdtimeoutd,
|
||||
wtimeoutd: wtimeoutd,
|
||||
}
|
||||
if ln, err = wrapTLS(scheme, tlsinfo, ln); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
type rwTimeoutListener struct {
|
||||
net.Listener
|
||||
wtimeoutd time.Duration
|
||||
rdtimeoutd time.Duration
|
||||
}
|
||||
|
||||
func (rwln *rwTimeoutListener) Accept() (net.Conn, error) {
|
||||
c, err := rwln.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return timeoutConn{
|
||||
Conn: c,
|
||||
wtimeoutd: rwln.wtimeoutd,
|
||||
rdtimeoutd: rwln.rdtimeoutd,
|
||||
}, nil
|
||||
}
|
||||
51
vendor/go.etcd.io/etcd/pkg/transport/timeout_transport.go
generated
vendored
Normal file
51
vendor/go.etcd.io/etcd/pkg/transport/timeout_transport.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewTimeoutTransport returns a transport created using the given TLS info.
|
||||
// If read/write on the created connection blocks longer than its time limit,
|
||||
// it will return timeout error.
|
||||
// If read/write timeout is set, transport will not be able to reuse connection.
|
||||
func NewTimeoutTransport(info TLSInfo, dialtimeoutd, rdtimeoutd, wtimeoutd time.Duration) (*http.Transport, error) {
|
||||
tr, err := NewTransport(info, dialtimeoutd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rdtimeoutd != 0 || wtimeoutd != 0 {
|
||||
// the timed out connection will timeout soon after it is idle.
|
||||
// it should not be put back to http transport as an idle connection for future usage.
|
||||
tr.MaxIdleConnsPerHost = -1
|
||||
} else {
|
||||
// allow more idle connections between peers to avoid unnecessary port allocation.
|
||||
tr.MaxIdleConnsPerHost = 1024
|
||||
}
|
||||
|
||||
tr.Dial = (&rwTimeoutDialer{
|
||||
Dialer: net.Dialer{
|
||||
Timeout: dialtimeoutd,
|
||||
KeepAlive: 30 * time.Second,
|
||||
},
|
||||
rdtimeoutd: rdtimeoutd,
|
||||
wtimeoutd: wtimeoutd,
|
||||
}).Dial
|
||||
return tr, nil
|
||||
}
|
||||
49
vendor/go.etcd.io/etcd/pkg/transport/tls.go
generated
vendored
Normal file
49
vendor/go.etcd.io/etcd/pkg/transport/tls.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ValidateSecureEndpoints scans the given endpoints against tls info, returning only those
|
||||
// endpoints that could be validated as secure.
|
||||
func ValidateSecureEndpoints(tlsInfo TLSInfo, eps []string) ([]string, error) {
|
||||
t, err := NewTransport(tlsInfo, 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var errs []string
|
||||
var endpoints []string
|
||||
for _, ep := range eps {
|
||||
if !strings.HasPrefix(ep, "https://") {
|
||||
errs = append(errs, fmt.Sprintf("%q is insecure", ep))
|
||||
continue
|
||||
}
|
||||
conn, cerr := t.Dial("tcp", ep[len("https://"):])
|
||||
if cerr != nil {
|
||||
errs = append(errs, fmt.Sprintf("%q failed to dial (%v)", ep, cerr))
|
||||
continue
|
||||
}
|
||||
conn.Close()
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
err = fmt.Errorf("%s", strings.Join(errs, ","))
|
||||
}
|
||||
return endpoints, err
|
||||
}
|
||||
71
vendor/go.etcd.io/etcd/pkg/transport/transport.go
generated
vendored
Normal file
71
vendor/go.etcd.io/etcd/pkg/transport/transport.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type unixTransport struct{ *http.Transport }
|
||||
|
||||
func NewTransport(info TLSInfo, dialtimeoutd time.Duration) (*http.Transport, error) {
|
||||
cfg, err := info.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: dialtimeoutd,
|
||||
// value taken from http.DefaultTransport
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
// value taken from http.DefaultTransport
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: cfg,
|
||||
}
|
||||
|
||||
dialer := (&net.Dialer{
|
||||
Timeout: dialtimeoutd,
|
||||
KeepAlive: 30 * time.Second,
|
||||
})
|
||||
dial := func(net, addr string) (net.Conn, error) {
|
||||
return dialer.Dial("unix", addr)
|
||||
}
|
||||
|
||||
tu := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: cfg,
|
||||
}
|
||||
ut := &unixTransport{tu}
|
||||
|
||||
t.RegisterProtocol("unix", ut)
|
||||
t.RegisterProtocol("unixs", ut)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (urt *unixTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
url := *req.URL
|
||||
req.URL = &url
|
||||
req.URL.Scheme = strings.Replace(req.URL.Scheme, "unix", "http", 1)
|
||||
return urt.Transport.RoundTrip(req)
|
||||
}
|
||||
40
vendor/go.etcd.io/etcd/pkg/transport/unix_listener.go
generated
vendored
Normal file
40
vendor/go.etcd.io/etcd/pkg/transport/unix_listener.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
type unixListener struct{ net.Listener }
|
||||
|
||||
func NewUnixListener(addr string) (net.Listener, error) {
|
||||
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
l, err := net.Listen("unix", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &unixListener{l}, nil
|
||||
}
|
||||
|
||||
func (ul *unixListener) Close() error {
|
||||
if err := os.Remove(ul.Addr().String()); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return ul.Listener.Close()
|
||||
}
|
||||
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
@ -2206,6 +2206,12 @@
|
||||
"revision": "eca5f03cea2ec6d275a4b86ca1aa764b9d6aaa84",
|
||||
"revisionTime": "2018-10-03T14:42:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ZrqOBeBTSsupblYMeANSpd3xyhQ=",
|
||||
"path": "go.etcd.io/etcd/clientv3/concurrency",
|
||||
"revision": "eca5f03cea2ec6d275a4b86ca1aa764b9d6aaa84",
|
||||
"revisionTime": "2018-10-03T14:42:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "SDdAjoQeeV8tEDv05QZ7/Yv79CI=",
|
||||
"path": "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes",
|
||||
@ -2254,6 +2260,12 @@
|
||||
"revision": "eca5f03cea2ec6d275a4b86ca1aa764b9d6aaa84",
|
||||
"revisionTime": "2018-10-03T14:42:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "YQD4i+Li76ogXqgKS62MJVf1agE=",
|
||||
"path": "go.etcd.io/etcd/pkg/transport",
|
||||
"revision": "eca5f03cea2ec6d275a4b86ca1aa764b9d6aaa84",
|
||||
"revisionTime": "2018-10-03T14:42:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "VL9HsNKbbCb1Ntj9VcFCfiTmjmM=",
|
||||
"path": "go.etcd.io/etcd/pkg/types",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user