MF-1179 - Add a certificate service and certs endpoint to SDK (#1188)

* adding certificate issuing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding cert endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs creation to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs creation to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs creation to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix env vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add volumes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix merge config for int

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove env

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cert test, change receiver to pointer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix var naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* correct error naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding certs service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change func receiever

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default cert issue method

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add config

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove some testing code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cert issue

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add vault api client

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add swagger for certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove certs from provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* clean provision from certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add list certificates endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add vault api in vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add revoke, fix bugs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix sdk for certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes, add env, doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes, add env, doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes, add env, doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove CA for signing from provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add docker file for certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix mock sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix RevokeCert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam ENV

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove tests temporarily

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove not needed envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors, add cli

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors, add cli, var rename

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix reviews, add viewcert, fix view all certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove view cert, as it will be retrieved from PKI

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default env val

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove some errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix revoking

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused field

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki, update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove methods, use fields

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments and package desc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments and package desc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
This commit is contained in:
Mirko Teodorovic
2020-07-21 12:53:21 +02:00
committed by GitHub
parent be1361311d
commit b91024465c
249 changed files with 31154 additions and 2421 deletions
+32 -2
View File
@@ -107,7 +107,6 @@ MF_PROVISION_CONFIG_FILE=/configs/config.toml
MF_PROVISION_LOG_LEVEL=debug
MF_PROVISION_HTTP_PORT=8190
MF_PROVISION_ENV_CLIENTS_TLS=false
MF_PROVISION_CA_CERTS=
MF_PROVISION_SERVER_CERT=
MF_PROVISION_SERVER_KEY=
MF_PROVISION_MQTT_URL=tcp://localhost
@@ -116,13 +115,44 @@ MF_PROVISION_THINGS_LOCATION=http://things:8182
MF_PROVISION_USER=
MF_PROVISION_PASS=
MF_PROVISION_API_KEY=
MF_PROVISION_CERTS_SVC_URL=http://localhost/certs
MF_PROVISION_CERTS_SVC_URL=http://localhost:8204
MF_PROVISION_X509_PROVISIONING=false
MF_PROVISION_BS_SVC_URL=http://bootstrap:8202/things/configs
MF_PROVISION_BS_SVC_WHITELIST_URL=http://bootstrap:8202/things/state
MF_PROVISION_BS_CONFIG_PROVISIONING=true
MF_PROVISION_BS_AUTO_WHITELIST=true
MF_PROVISION_BS_CONTENT=
MF_PROVISION_CERTS_RSA_BITS=4096
MF_PROVISION_CERTS_HOURS_VALID=2400h
# Certs
MF_CERTS_LOG_LEVEL=debug
MF_CERTS_HTTP_PORT=8204
MF_CERTS_DB_HOST=certs-db
MF_CERTS_DB_PORT=5432
MF_CERTS_DB_USER=mainflux
MF_CERTS_DB_PASS=mainflux
MF_CERTS_DB=certs
MF_CERTS_DB_SSL_MODE=
MF_CERTS_DB_SSL_CERT=
MF_CERTS_DB_SSL_KEY=
MF_CERTS_DB_SSL_ROOT_CERT=
MF_CERTS_ENCRYPT_KEY=
MF_CERTS_CLIENT_TLS=
MF_CERTS_CA_CERTS=
MF_CERTS_SERVER_CERT=
MF_CERTS_SERVER_KEY=
MF_SDK_BASE_URL=http://172.17.0.1
MF_SDK_THINGS_PREFIX=
MF_CERTS_SIGN_CA_PATH=/etc/ssl/certs/ca.crt
MF_CERTS_SIGN_CA_KEY_PATH=/etc/ssl/certs/ca.key
MF_CERTS_SIGN_HOURS_VALID=2048h
MF_CERTS_SIGN_RSA_BITS=2048
MF_CERTS_VAULT_HOST=
MF_CERTS_VAULT_PKI_PATH=pki_int
MF_CERTS_VAULT_ROLE=agent
MF_CERTS_VAULT_TOKEN=s.nArgw6xn3uIOfA7nfKk8LFaW
### LoRa
MF_LORA_ADAPTER_LOG_LEVEL=debug
+1 -1
View File
@@ -5,7 +5,7 @@ MF_DOCKER_IMAGE_NAME_PREFIX ?= mainflux
BUILD_DIR = build
SERVICES = users things http coap lora influxdb-writer influxdb-reader mongodb-writer \
mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader cli \
bootstrap opcua authn twins mqtt provision
bootstrap opcua authn twins mqtt provision certs
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
+1 -1
View File
@@ -68,7 +68,7 @@ func TestMain(m *testing.M) {
code := m.Run()
// defers will not be run when using os.Exit
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
+1 -1
View File
@@ -72,7 +72,7 @@ func TestMain(m *testing.M) {
code := m.Run()
// defers will not be run when using os.Exit
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
+27
View File
@@ -0,0 +1,27 @@
# CERTS Service
Issues certificates for things. `Certs` service can create certificates to be used when `Mainflux` is deployed to support mTLS.
Certificate service can create certificates in two modes:
1. Development mode - to be used when no PKI is deployed, this works similar to the [make thing_cert](../docker/ssl/Makefile)
2. PKI mode - certificates issued by PKI, when you deploy `Vault` as PKI certificate management `cert` service will proxy requests to `Vault` previously checking access rights and saving info on successfully created certificate.
If `MF_CERTS_VAULT_HOST` is empty than Development mode is on.
To issue a certificate:
```bash
curl -s -S -X POST http://localhost:8204/certs -H "Authorization: $TOK" -H 'Content-Type: application/json' -d '{"thing_id":<thing_id>, "rsa_bits":2048, "key_type":"rsa"}'
```
```json
{
"ThingID": "",
"ClientCert": "-----BEGIN CERTIFICATE-----\nMIIDmTCCAoGgAwIBAgIRANmkAPbTR1UYeYO0Id/4+8gwDQYJKoZIhvcNAQELBQAw\nVzESMBAGA1UEAwwJbG9jYWxob3N0MREwDwYDVQQKDAhNYWluZmx1eDEMMAoGA1UE\nCwwDSW9UMSAwHgYJKoZIhvcNAQkBFhFpbmZvQG1haW5mbHV4LmNvbTAeFw0yMDA2\nMzAxNDIxMDlaFw0yMDA5MjMyMjIxMDlaMFUxETAPBgNVBAoTCE1haW5mbHV4MREw\nDwYDVQQLEwhtYWluZmx1eDEtMCsGA1UEAxMkYjAwZDBhNzktYjQ2YS00NTk3LTli\nNGYtMjhkZGJhNTBjYTYyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\ntgS2fLUWG3CCQz/l6VRQRJfRvWmdxK0mW6zIXGeeOILYZeaLiuiUnohwMJ4RiMqT\nuJbInAIuO/Tt5osfrCFFzPEOLYJ5nZBBaJfTIAxqf84Ou1oeMRll4wpzgeKx0rJO\nXMAARwn1bT9n3uky5QQGSLy4PyyILzSXH/1yCQQctdQB/Ar/UI1TaYoYlGzh7dHT\nWpcxq1HYgCyAtcrQrGD0rEwUn82UBCrnya+bygNqu0oDzIFQwa1G8jxSgXk0mFS1\nWrk7rBipsvp8HQhdnvbEVz4k4AAKcQxesH4DkRx/EXmU2UvN3XysvcJ2bL+UzMNI\njNhAe0pgPbB82F6zkYZ/XQIDAQABo2IwYDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0l\nBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQHBAUBAgMEBjAfBgNVHSME\nGDAWgBRs4xR91qEjNRGmw391xS7x6Tc+8jANBgkqhkiG9w0BAQsFAAOCAQEAW/dS\nV4vNLTZwBnPVHUX35pRFxPKvscY+vnnpgyDtITgZHYe0KL+Bs3IHuywtqaezU5x1\nkZo+frE1OcpRvp7HJtDiT06yz+18qOYZMappCWCeAFWtZkMhlvnm3TqTkgui6Xgl\nGj5xnPb15AOlsDE2dkv5S6kEwJGHdVX6AOWfB4ubUq5S9e4ABYzXGUty6Hw/ZUmJ\nhCTRVJ7cQJVTJsl1o7CYT8JBvUUG75LirtoFE4M4JwsfsKZXzrQffTf1ynqI3dN/\nHWySEbvTSWcRcA3MSmOTxGt5/zwCglHDlWPKMrXtjTW7NPuGL5/P9HSB9HGVVeET\nDUMdvYwgj0cUCEu3LA==\n-----END CERTIFICATE-----\n",
"IssuingCA": "",
"CAChain": null,
"ClientKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtgS2fLUWG3CCQz/l6VRQRJfRvWmdxK0mW6zIXGeeOILYZeaL\niuiUnohwMJ4RiMqTuJbInAIuO/Tt5osfrCFFzPEOLYJ5nZBBaJfTIAxqf84Ou1oe\nMRll4wpzgeKx0rJOXMAARwn1bT9n3uky5QQGSLy4PyyILzSXH/1yCQQctdQB/Ar/\nUI1TaYoYlGzh7dHTWpcxq1HYgCyAtcrQrGD0rEwUn82UBCrnya+bygNqu0oDzIFQ\nwa1G8jxSgXk0mFS1Wrk7rBipsvp8HQhdnvbEVz4k4AAKcQxesH4DkRx/EXmU2UvN\n3XysvcJ2bL+UzMNIjNhAe0pgPbB82F6zkYZ/XQIDAQABAoIBAALoal3tqq+/iWU3\npR2oKiweXMxw3oNg3McEKKNJSH7QoFJob3xFoPIzbc9pBxCvY9LEHepYIpL0o8RW\nHqhqU6olg7t4ZSb+Qf1Ax6+wYxctnJCjrO3N4RHSfevqSjr6fEQBEUARSal4JNmr\n0hNUkCEjWrIvrPFMHsn1C5hXR3okJQpGsad4oCGZDp2eZ/NDyvmLBLci9/5CJdRv\n6roOF5ShWweKcz1+pfy666Q8RiUI7H1zXjPaL4yqkv8eg/WPOO0dYF2Ri2Grk9OY\n1qTM0W1vi9zfncinZ0DpgtwMTFQezGwhUyJHSYHmjVBA4AaYIyOQAI/2dl5fXM+O\n9JfXpOUCgYEA10xAtMc/8KOLbHCprpc4pbtOqfchq/M04qPKxQNAjqvLodrWZZgF\nexa+B3eWWn5MxmQMx18AjBCPwbNDK8Rkd9VqzdWempaSblgZ7y1a0rRNTXzN5DFP\noiuRQV4wszCuj5XSdPn+lxApaI/4+TQ0oweIZCpGW39XKePPoB5WZiMCgYEA2G3W\niJncRpmxWwrRPi1W26E9tWOT5s9wYgXWMc+PAVUd/qdDRuMBHpu861Qoghp/MJog\nBYqt2rQqU0OxvIXlXPrXPHXrCLOFwybRCBVREZrg4BZNnjyDTLOu9C+0M3J9ImCh\n3vniYqb7S0gRmoDM0R3Zu4+ajfP2QOGLXw1qHH8CgYEAl0EQ7HBW8V5UYzi7XNcM\nixKOb0YZt83DR74+hC6GujTjeLBfkzw8DX+qvWA8lxLIKVC80YxivAQemryv4h21\nX6Llx/nd1UkXUsI+ZhP9DK5y6I9XroseIRZuk/fyStFWsbVWB6xiOgq2rKkJBzqw\nCCEQpx40E6/gsqNDiIAHvvUCgYBkkjXc6FJ55DWMLuyozfzMtpKsVYeG++InSrsM\nDn1PizQS/7q9mAMPLCOP312rh5CPDy/OI3FCbfI1GwHerwG0QUP/bnQ3aOTBmKoN\n7YnsemIA/5w16bzBycWE5x3/wjXv4aOWr9vJJ/siMm0rtKp4ijyBcevKBxHpeGWB\nWAR1FQKBgGIqAxGnBpip9E24gH894BaGHHMpQCwAxARev6sHKUy27eFUd6ipoTva\n4Wv36iz3gxU4R5B0gyfnxBNiUab/z90cb5+6+FYO13kqjxRRZWffohk5nHlmFN9K\nea7KQHTfTdRhOLUzW2yVqLi9pzfTfA6Yqf3U1YD3bgnWrp1VQnjo\n-----END RSA PRIVATE KEY-----\n",
"PrivateKeyType": "",
"Serial": "",
"Expire": "0001-01-01T00:00:00Z"
}
```
To revoke a certificate
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package api contains implementation of certs service HTTP API.
package api
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/certs"
)
func issueCert(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(addCertsReq)
if err := req.validate(); err != nil {
return nil, err
}
res, err := svc.IssueCert(ctx, req.token, req.ThingID, req.Valid, req.KeyBits, req.KeyType)
if err != nil {
return certsResponse{Error: err.Error()}, nil
}
return res, nil
}
}
func listCerts(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listReq)
if err := req.validate(); err != nil {
return nil, err
}
page, err := svc.ListCerts(ctx, req.token, req.offset, req.limit)
if err != nil {
return certsPageRes{
Error: err.Error(),
}, err
}
res := certsPageRes{
pageRes: pageRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
},
Certs: []certsResponse{},
}
for _, cert := range page.Certs {
view := certsResponse{
Serial: cert.Serial,
ThingID: cert.ThingID,
}
res.Certs = append(res.Certs, view)
}
return res, nil
}
}
func revokeCert(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(revokeReq)
if err := req.validate(); err != nil {
return nil, err
}
return svc.RevokeCert(ctx, req.token, req.ThingID)
}
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"fmt"
"time"
"github.com/mainflux/mainflux/certs"
log "github.com/mainflux/mainflux/logger"
)
var _ certs.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger log.Logger
svc certs.Service
}
// NewLoggingMiddleware adds logging facilities to the core service.
func NewLoggingMiddleware(svc certs.Service, logger log.Logger) certs.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) IssueCert(ctx context.Context, token, thingID, daysValid string, keyBits int, keyType string) (c certs.Cert, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method issue_cert for token: %s and thing: %s took %s to complete", token, thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.IssueCert(ctx, token, thingID, daysValid, keyBits, keyType)
}
func (lm *loggingMiddleware) ListCerts(ctx context.Context, token string, offset, limit uint64) (cp certs.Page, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_certs for token: %s took %s to complete", token, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListCerts(ctx, token, offset, limit)
}
func (lm *loggingMiddleware) RevokeCert(ctx context.Context, token, thingID string) (c certs.Revoke, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method revoke_cert for token: %s and thing: %s took %s to complete", token, thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.RevokeCert(ctx, token, thingID)
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"time"
"github.com/go-kit/kit/metrics"
"github.com/mainflux/mainflux/certs"
)
var _ certs.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc certs.Service
}
// MetricsMiddleware instruments core service by tracking request count and
// latency.
func MetricsMiddleware(svc certs.Service, counter metrics.Counter, latency metrics.Histogram) certs.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (ms *metricsMiddleware) IssueCert(ctx context.Context, token, thingID string, daysValid string, keyBits int, keyType string) (certs.Cert, error) {
defer func(begin time.Time) {
ms.counter.With("method", "issue_cert").Add(1)
ms.latency.With("method", "issue_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.IssueCert(ctx, token, thingID, daysValid, keyBits, keyType)
}
func (ms *metricsMiddleware) ListCerts(ctx context.Context, token string, offset, limit uint64) (certs.Page, error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_certs").Add(1)
ms.latency.With("method", "list_certs").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListCerts(ctx, token, offset, limit)
}
func (ms *metricsMiddleware) RevokeCert(ctx context.Context, token, thingID string) (certs.Revoke, error) {
defer func(begin time.Time) {
ms.counter.With("method", "revoke_cert").Add(1)
ms.latency.With("method", "revoke_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.RevokeCert(ctx, token, thingID)
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import "github.com/mainflux/mainflux/certs"
const maxLimitSize = 100
type addCertsReq struct {
token string
ThingID string `json:"thing_id"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
Valid string `json:"valid"`
}
func (req addCertsReq) validate() error {
if req.ThingID == "" && req.token == "" {
return errUnauthorized
}
return nil
}
type listReq struct {
token string
offset uint64
limit uint64
}
func (req *listReq) validate() error {
if req.token == "" {
return certs.ErrUnauthorizedAccess
}
if req.limit == 0 || req.limit > maxLimitSize {
return certs.ErrMalformedEntity
}
return nil
}
type revokeReq struct {
token string
ThingID string `json:"thing_id"`
}
func (req *revokeReq) validate() error {
if req.token == "" || req.ThingID == "" {
return certs.ErrUnauthorizedAccess
}
return nil
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"net/http"
)
type pageRes struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
}
type certsPageRes struct {
pageRes
Certs []certsResponse `json:"certs"`
Error string `json:"error,omitempty"`
}
type certsResponse struct {
ClientCert map[string]string `json:"client_cert"`
ClientKey map[string]string `json:"client_key"`
Serial string `json:"serial"`
ThingID string `json:"thing_id"`
CACert string `json:"ca_cert"`
Error string `json:"error"`
}
func (res certsPageRes) Code() int {
return http.StatusCreated
}
func (res certsPageRes) Headers() map[string]string {
return map[string]string{}
}
func (res certsPageRes) Empty() bool {
return false
}
func (res certsResponse) Code() int {
return http.StatusCreated
}
func (res certsResponse) Headers() map[string]string {
return map[string]string{}
}
func (res certsResponse) Empty() bool {
return false
}
+174
View File
@@ -0,0 +1,174 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"encoding/json"
"io"
"net/http"
"strconv"
"github.com/mainflux/mainflux/certs"
"github.com/mainflux/mainflux/pkg/errors"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
contentType = "application/json"
offset = "offset"
limit = "limit"
defOffset = 0
defLimit = 10
)
var (
errUnsupportedContentType = errors.New("unsupported content type")
errUnauthorized = errors.New("missing or invalid credentials provided")
errInvalidQueryParams = errors.New("invalid query params")
errMalformedEntity = errors.New("malformed entity")
errConflict = errors.New("entity already exists")
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc certs.Service) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(encodeError),
}
r := bone.New()
r.Post("/certs", kithttp.NewServer(
issueCert(svc),
decodeCerts,
encodeResponse,
opts...,
))
r.Get("/certs", kithttp.NewServer(
listCerts(svc),
decodeListCerts,
encodeResponse,
opts...,
))
r.Delete("/certs/revoke", kithttp.NewServer(
revokeCert(svc),
decodeRevokeCerts,
encodeResponse,
opts...,
))
r.Handle("/metrics", promhttp.Handler())
r.GetFunc("/version", mainflux.Version("certs"))
return r
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
if ar, ok := response.(mainflux.Response); ok {
for k, v := range ar.Headers() {
w.Header().Set(k, v)
}
w.WriteHeader(ar.Code())
if ar.Empty() {
return nil
}
}
return json.NewEncoder(w).Encode(response)
}
func decodeListCerts(_ context.Context, r *http.Request) (interface{}, error) {
l, err := readUintQuery(r, limit, defLimit)
if err != nil {
return nil, err
}
o, err := readUintQuery(r, offset, defOffset)
if err != nil {
return nil, err
}
req := listReq{
token: r.Header.Get("Authorization"),
limit: l,
offset: o,
}
return req, nil
}
func readUintQuery(r *http.Request, key string, def uint64) (uint64, error) {
vals := bone.GetQuery(r, key)
if len(vals) > 1 {
return 0, errInvalidQueryParams
}
if len(vals) == 0 {
return def, nil
}
strval := vals[0]
val, err := strconv.ParseUint(strval, 10, 64)
if err != nil {
return 0, errInvalidQueryParams
}
return val, nil
}
func decodeCerts(_ context.Context, r *http.Request) (interface{}, error) {
if r.Header.Get("Content-Type") != contentType {
return nil, errUnsupportedContentType
}
req := addCertsReq{token: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeRevokeCerts(_ context.Context, r *http.Request) (interface{}, error) {
if r.Header.Get("Content-Type") != contentType {
return nil, errUnsupportedContentType
}
req := revokeReq{token: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.Header().Set("Content-Type", contentType)
switch err {
case errUnsupportedContentType:
w.WriteHeader(http.StatusUnsupportedMediaType)
case io.EOF, errMalformedEntity:
w.WriteHeader(http.StatusBadRequest)
case errConflict:
w.WriteHeader(http.StatusConflict)
default:
switch err.(type) {
case *json.SyntaxError:
w.WriteHeader(http.StatusBadRequest)
case *json.UnmarshalTypeError:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package certs
import "context"
// ConfigsPage contains page related metadata as well as list
type Page struct {
Total uint64
Offset uint64
Limit uint64
Certs []Cert
}
// Repository specifies a Config persistence API.
type Repository interface {
// Save saves cert for thing into database
Save(ctx context.Context, cert Cert) (string, error)
// RetrieveAll retrieve all issued certificates for given owner
RetrieveAll(ctx context.Context, ownerID string, offset, limit uint64) (Page, error)
// Remove certificate from DB for given thing
Remove(ctx context.Context, thingID string) error
// RetrieveByThing certificate by given thing
RetrieveByThing(ctx context.Context, thingID string) (Cert, error)
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package certs contains the domain concept definitions needed to support
// Mainflux certs service functionality.
package certs
+28
View File
@@ -0,0 +1,28 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package pki wraps vault client
package pki
import "time"
type Revoke struct {
RevocationTime time.Time `mapstructure:"revocation_time"`
}
type Cert struct {
ClientCert string `json:"client_cert" mapstructure:"certificate"`
IssuingCA string `json:"issuing_ca" mapstructure:"issuing_ca"`
CAChain []string `json:"ca_chain" mapstructure:"ca_chain"`
ClientKey string `json:"client_key" mapstructure:"private_key"`
PrivateKeyType string `json:"private_key_type" mapstructure:"private_key_type"`
Serial string `json:"serial" mapstructure:"serial_number"`
Expire time.Time `json:"expire" mapstructure:"-"`
}
type Agent interface {
// IssueCert issues certificate on PKI
IssueCert(cn string, ttl, keyType string, keyBits int) (Cert, error)
// Revoke revokes certificate from PKI
Revoke(serial string) (Revoke, error)
}
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package pki wraps vault client
package pki
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/hashicorp/vault/api"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mitchellh/mapstructure"
)
const (
issue = "issue"
revoke = "revoke"
apiVer = "v1"
)
var (
errFailedVaultCertIssue = errors.New("failed to issue vault certificate")
errFailedCertDecoding = errors.New("failed to decode response from vault service")
)
type pkiAgent struct {
token string
path string
role string
host string
issueURL string
revokeURL string
client *api.Client
}
type certReq struct {
CommonName string `json:"common_name"`
TTL string `json:"ttl"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
}
type certRevokeReq struct {
SerialNumber string `json:"serial_number"`
}
func NewVaultClient(token, host, path, role string) (Agent, error) {
conf := &api.Config{
Address: host,
}
client, err := api.NewClient(conf)
if err != nil {
return nil, err
}
client.SetToken(token)
p := pkiAgent{
token: token,
host: host,
role: role,
path: path,
client: client,
issueURL: "/" + apiVer + "/" + path + "/" + issue + "/" + role,
revokeURL: "/" + apiVer + "/" + path + "/" + revoke,
}
return &p, nil
}
func (p *pkiAgent) IssueCert(cn string, ttl, keyType string, keyBits int) (Cert, error) {
cReq := certReq{
CommonName: cn,
TTL: ttl,
KeyBits: keyBits,
KeyType: keyType,
}
r := p.client.NewRequest("POST", p.issueURL)
if err := r.SetJSONBody(cReq); err != nil {
return Cert{}, err
}
resp, err := p.client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return Cert{}, err
}
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Cert{}, err
}
return Cert{}, errors.Wrap(errFailedVaultCertIssue, err)
}
s, _ := api.ParseSecret(resp.Body)
cert := Cert{}
if err = mapstructure.Decode(s.Data, &cert); err != nil {
return Cert{}, errors.Wrap(errFailedCertDecoding, err)
}
// Expire time calc must be revised
// value doesnt look correct
exp, err := s.Data["expiration"].(json.Number).Float64()
if err != nil {
return cert, err
}
expTime := time.Unix(0, int64(exp)*int64(time.Millisecond))
cert.Expire = expTime
return cert, nil
}
func (p *pkiAgent) Revoke(serial string) (Revoke, error) {
cReq := certRevokeReq{
SerialNumber: serial,
}
r := p.client.NewRequest("POST", p.revokeURL)
if err := r.SetJSONBody(cReq); err != nil {
return Revoke{}, err
}
resp, err := p.client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return Revoke{}, err
}
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Revoke{}, err
}
return Revoke{}, errors.Wrap(errFailedVaultCertIssue, err)
}
s, err := api.ParseSecret(resp.Body)
if err != nil {
return Revoke{}, err
}
rev, err := s.Data["revocation_time"].(json.Number).Float64()
if err != nil {
return Revoke{}, err
}
revTime := time.Unix(0, int64(rev)*int64(time.Millisecond))
return Revoke{
RevocationTime: revTime,
}, nil
}
+196
View File
@@ -0,0 +1,196 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/mainflux/mainflux/certs"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/things"
)
const duplicateErr = "unique_violation"
var (
errSaveDB = errors.New("failed to save certificate to database")
errRetrieveDB = errors.New("failed to retrieve certificate from db")
errRemove = errors.New("failed to remove certificate from database")
errInvalid = "invalid_text_representation"
)
var _ certs.Repository = (*certsRepository)(nil)
type Cert struct {
ThingID string
Serial string
Expire time.Time
}
type certsRepository struct {
db *sqlx.DB
log logger.Logger
}
// NewRepository instantiates a PostgreSQL implementation of certs
// repository.
func NewRepository(db *sqlx.DB, log logger.Logger) certs.Repository {
return &certsRepository{db: db, log: log}
}
func (cr certsRepository) RetrieveAll(ctx context.Context, ownerID string, offset, limit uint64) (certs.Page, error) {
q := `SELECT thing_id, owner_id, serial, expire FROM certs WHERE owner_id = $1 ORDER BY expire LIMIT $2 OFFSET $3;`
rows, err := cr.db.Query(q, ownerID, limit, offset)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve configs due to %s", err))
return certs.Page{}, err
}
defer rows.Close()
certificates := []certs.Cert{}
for rows.Next() {
c := certs.Cert{}
if err := rows.Scan(&c.ThingID, &c.OwnerID, &c.Serial, &c.Expire); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
return certs.Page{}, err
}
certificates = append(certificates, c)
}
q = `SELECT COUNT(*) FROM certs WHERE owner_id = $1`
var total uint64
if err := cr.db.QueryRow(q, ownerID).Scan(&total); err != nil {
cr.log.Error(fmt.Sprintf("Failed to count certs due to %s", err))
return certs.Page{}, err
}
return certs.Page{
Total: total,
Limit: limit,
Offset: offset,
Certs: certificates,
}, nil
}
func (cr certsRepository) Save(ctx context.Context, cert certs.Cert) (string, error) {
q := `INSERT INTO certs (thing_id, owner_id, serial, expire) VALUES (:thing_id, :owner_id, :serial, :expire)`
tx, err := cr.db.Beginx()
if err != nil {
return "", errors.Wrap(errSaveDB, err)
}
dbcrt := toDBCert(cert)
if _, err := tx.NamedExec(q, dbcrt); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = errors.New("error conflict")
}
cr.rollback("Failed to insert a Cert", tx, err)
return "", errors.Wrap(errSaveDB, e)
}
if err := tx.Commit(); err != nil {
cr.rollback("Failed to commit Config save", tx, err)
}
return cert.Serial, nil
}
func (cr certsRepository) Remove(ctx context.Context, serial string) error {
if _, err := cr.retrieveBySerial(ctx, serial); err != nil {
return errors.Wrap(errRemove, err)
}
q := `DELETE FROM certs WHERE serial = :serial`
var c certs.Cert
c.Serial = serial
dbcrt := toDBCert(c)
if _, err := cr.db.NamedExecContext(ctx, q, dbcrt); err != nil {
return errors.Wrap(errRemove, err)
}
return nil
}
func (cr certsRepository) RetrieveByThing(ctx context.Context, thingID string) (certs.Cert, error) {
q := `SELECT thing_id, owner_id, serial, expire FROM certs WHERE thing_id = $1`
var dbcrt dbCert
var c certs.Cert
if err := cr.db.QueryRowxContext(ctx, q, thingID).StructScan(&dbcrt); err != nil {
pqErr, ok := err.(*pq.Error)
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
return c, errors.Wrap(things.ErrNotFound, err)
}
return c, errors.Wrap(errRetrieveDB, err)
}
c = toCert(dbcrt)
return c, nil
}
func (cr certsRepository) retrieveBySerial(ctx context.Context, serial string) (certs.Cert, error) {
q := `SELECT thing_id, owner_id, serial, expire FROM certs WHERE serial = $1`
var dbcrt dbCert
var c certs.Cert
if err := cr.db.QueryRowxContext(ctx, q, serial).StructScan(&dbcrt); err != nil {
pqErr, ok := err.(*pq.Error)
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
return c, errors.Wrap(things.ErrNotFound, err)
}
return c, errors.Wrap(errRetrieveDB, err)
}
c = toCert(dbcrt)
return c, nil
}
func (cr certsRepository) rollback(content string, tx *sqlx.Tx, err error) {
cr.log.Error(fmt.Sprintf("%s %s", content, err))
if err := tx.Rollback(); err != nil {
cr.log.Error(fmt.Sprintf("Failed to rollback due to %s", err))
}
}
type dbCert struct {
ThingID string `db:"thing_id"`
Serial string `db:"serial"`
Expire time.Time `db:"expire"`
OwnerID string `db:"owner_id"`
}
func toDBCert(c certs.Cert) dbCert {
return dbCert{
ThingID: c.ThingID,
OwnerID: c.OwnerID,
Serial: c.Serial,
Expire: c.Expire,
}
}
func toCert(cdb dbCert) certs.Cert {
var c certs.Cert
c.OwnerID = cdb.OwnerID
c.ThingID = cdb.ThingID
c.Serial = cdb.Serial
c.Expire = cdb.Expire
return c
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package postgres contains repository implementations using PostgreSQL as
// the underlying database.
package postgres
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"errors"
"fmt"
"github.com/jmoiron/sqlx"
migrate "github.com/rubenv/sql-migrate"
)
const primaryKey = "primary_key"
// ErrMigrate indicates error during database migrations.
var ErrMigrate = errors.New("error executing database migrations")
// Config defines the options that are used when connecting to a PostgreSQL instance
type Config struct {
Host string
Port string
User string
Pass string
Name string
SSLMode string
SSLCert string
SSLKey string
SSLRootCert string
}
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate
// failure.
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
}
if err := migrateDB(db); err != nil {
mErr, ok := err.(*migrate.TxError)
if ok && mErr.Migration.Id == primaryKey {
return db, ErrMigrate
}
return nil, err
}
return db, nil
}
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "certs_1",
Up: []string{
`CREATE TABLE IF NOT EXISTS certs (
thing_id TEXT NOT NULL,
owner_id TEXT NOT NULL,
expire TIMESTAMPTZ NOT NULL,
serial TEXT NOT NULL,
PRIMARY KEY (thing_id, owner_id)
);`,
},
Down: []string{
"DROP TABLE IF EXISTS certs;",
},
},
},
}
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres_test
import (
"fmt"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux/certs/postgres"
"github.com/mainflux/mainflux/logger"
dockertest "github.com/ory/dockertest/v3"
)
var (
testLog, _ = logger.New(os.Stdout, logger.Info.String())
db *sqlx.DB
)
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
return
}
cfg := []string{
"POSTGRES_USER=test",
"POSTGRES_PASSWORD=test",
"POSTGRES_DB=test",
}
container, err := pool.Run("postgres", "10.2-alpine", cfg)
if err != nil {
testLog.Error(fmt.Sprintf("Could not start container: %s", err))
}
port := container.GetPort("5432/tcp")
if err := pool.Retry(func() error {
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
db, err = sqlx.Open("postgres", url)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
dbConfig := postgres.Config{
Host: "localhost",
Port: port,
User: "test",
Pass: "test",
Name: "test",
SSLMode: "disable",
SSLCert: "",
SSLKey: "",
SSLRootCert: "",
}
if db, err = postgres.Connect(dbConfig); err != nil {
testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err))
}
code := m.Run()
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
testLog.Error(fmt.Sprintf("Could not purge container: %s", err))
}
os.Exit(code)
}
+303
View File
@@ -0,0 +1,303 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package certs
import (
"bufio"
"bytes"
"context"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/certs/pki"
"github.com/mainflux/mainflux/pkg/errors"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
)
var (
// ErrNotFound indicates a non-existent entity request.
ErrNotFound = errors.New("non-existent entity")
// ErrMalformedEntity indicates malformed entity specification.
ErrMalformedEntity = errors.New("malformed entity specification")
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
// when accessing a protected resource.
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
errFailedKeyCreation = errors.New("failed to create client private key")
errFailedDateSetting = errors.New("failed to set date for certificate")
errKeyBitsValueWrong = errors.New("missing RSA bits for certificate creation")
errMissingCACertificate = errors.New("missing CA certificate for certificate signing")
errFailedSerialGeneration = errors.New("failed to generate certificate serial")
errFailedPemKeyWrite = errors.New("failed to write PEM key")
errFailedPemDataWrite = errors.New("failed to write pem data for certificate")
errPrivateKeyUnsupportedType = errors.New("private key type is unsupported")
errPrivateKeyEmpty = errors.New("private key is empty")
errFailedToRemoveCertFromDB = errors.New("failed to remove cert serial from db")
errFailedCertCreation = errors.New("failed to create client certificate")
errFailedCertRevocation = errors.New("failed to revoke certificate")
)
var _ Service = (*certsService)(nil)
// Service specifies an API that must be fulfilled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// IssueCert issues certificate for given thing id if access is granted with token
IssueCert(ctx context.Context, token, thingID, daysValid string, keyBits int, keyType string) (Cert, error)
// ListCerts lists all certificates issued for given owner
ListCerts(ctx context.Context, token string, offset, limit uint64) (Page, error)
// RevokeCert revokes certificate for given thing
RevokeCert(ctx context.Context, token, thingID string) (Revoke, error)
}
type Config struct {
LogLevel string
ClientTLS bool
CaCerts string
HTTPPort string
ServerCert string
ServerKey string
BaseURL string
ThingsPrefix string
JaegerURL string
AuthnURL string
AuthnTimeout time.Duration
SignTLSCert tls.Certificate
SignX509Cert *x509.Certificate
SignRSABits int
SignHoursValid string
PKIHost string
PKIPath string
PKIRole string
PKIToken string
}
type certsService struct {
auth mainflux.AuthNServiceClient
certsRepo Repository
sdk mfsdk.SDK
conf Config
pki pki.Agent
}
// New returns new Certs service.
func New(auth mainflux.AuthNServiceClient, certs Repository, sdk mfsdk.SDK, config Config, pki pki.Agent) Service {
return &certsService{
certsRepo: certs,
sdk: sdk,
auth: auth,
conf: config,
pki: pki,
}
}
type Revoke struct {
RevocationTime time.Time `mapstructure:"revocation_time"`
}
type Cert struct {
OwnerID string `json:"owner_id" mapstructure:"owner_id"`
ThingID string `json:"thing_id" mapstructure:"thing_id"`
ClientCert string `json:"client_cert" mapstructure:"certificate"`
IssuingCA string `json:"issuing_ca" mapstructure:"issuing_ca"`
CAChain []string `json:"ca_chain" mapstructure:"ca_chain"`
ClientKey string `json:"client_key" mapstructure:"private_key"`
PrivateKeyType string `json:"private_key_type" mapstructure:"private_key_type"`
Serial string `json:"serial" mapstructure:"serial_number"`
Expire time.Time `json:"expire" mapstructure:"-"`
}
func (cs *certsService) IssueCert(ctx context.Context, token, thingID string, daysValid string, keyBits int, keyType string) (Cert, error) {
var c Cert
owner, err := cs.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return c, errors.Wrap(ErrUnauthorizedAccess, err)
}
thing, err := cs.sdk.Thing(thingID, token)
if err != nil {
return c, errors.Wrap(errFailedCertCreation, err)
}
// If PKIClient == nil we don't use 3rd party PKI service.
if cs.conf.PKIHost == "" {
c.ClientCert, c.ClientKey, err = cs.certs(thing.Key, daysValid, keyBits)
if err != nil {
return c, errors.Wrap(errFailedCertCreation, err)
}
return c, err
}
cert, err := cs.pki.IssueCert(thingID, daysValid, keyType, keyBits)
if err != nil {
return c, errors.Wrap(errFailedCertCreation, err)
}
c.ThingID = thingID
c.OwnerID = owner.GetValue()
c.ClientCert = cert.ClientCert
c.IssuingCA = cert.IssuingCA
c.CAChain = cert.CAChain
c.ClientKey = cert.ClientKey
c.PrivateKeyType = cert.PrivateKeyType
c.Serial = cert.Serial
c.Expire = cert.Expire
_, err = cs.certsRepo.Save(context.Background(), c)
return c, err
}
func (cs *certsService) RevokeCert(ctx context.Context, token, thingID string) (Revoke, error) {
var revoke Revoke
_, err := cs.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return revoke, errors.Wrap(ErrUnauthorizedAccess, err)
}
thing, err := cs.sdk.Thing(thingID, token)
if err != nil {
return revoke, errors.Wrap(errFailedCertRevocation, err)
}
cert, err := cs.certsRepo.RetrieveByThing(ctx, thing.ID)
if err != nil {
return revoke, errors.Wrap(errFailedCertRevocation, err)
}
r, err := cs.pki.Revoke(cert.Serial)
if err != nil {
return revoke, errors.Wrap(errFailedCertRevocation, err)
}
revoke.RevocationTime = r.RevocationTime
if err = cs.certsRepo.Remove(context.Background(), cert.Serial); err != nil {
return revoke, errors.Wrap(errFailedToRemoveCertFromDB, err)
}
return revoke, nil
}
func (cs *certsService) ListCerts(ctx context.Context, token string, offset, limit uint64) (Page, error) {
u, err := cs.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return Page{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return cs.certsRepo.RetrieveAll(ctx, u.GetValue(), offset, limit)
}
func (cs *certsService) certs(thingKey, daysValid string, keyBits int) (string, string, error) {
if cs.conf.SignX509Cert == nil {
return "", "", errors.Wrap(errFailedCertCreation, errMissingCACertificate)
}
if keyBits == 0 {
return "", "", errors.Wrap(errFailedCertCreation, errKeyBitsValueWrong)
}
var priv interface{}
priv, err := rsa.GenerateKey(rand.Reader, keyBits)
if err != nil {
return "", "", errors.Wrap(errFailedKeyCreation, err)
}
if daysValid == "" {
daysValid = cs.conf.SignHoursValid
}
notBefore := time.Now()
validFor, err := time.ParseDuration(daysValid)
if err != nil {
return "", "", errors.Wrap(errFailedDateSetting, err)
}
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return "", "", errors.Wrap(errFailedSerialGeneration, err)
}
tmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Mainflux"},
CommonName: thingKey,
OrganizationalUnit: []string{"mainflux"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
SubjectKeyId: []byte{1, 2, 3, 4, 6},
}
pubKey, err := publicKey(priv)
if err != nil {
return "", "", errors.Wrap(errFailedCertCreation, err)
}
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, cs.conf.SignX509Cert, pubKey, cs.conf.SignTLSCert.PrivateKey)
if err != nil {
return "", "", errors.Wrap(errFailedCertCreation, err)
}
var bw, keyOut bytes.Buffer
buffWriter := bufio.NewWriter(&bw)
buffKeyOut := bufio.NewWriter(&keyOut)
if err := pem.Encode(buffWriter, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return "", "", errors.Wrap(errFailedPemDataWrite, err)
}
buffWriter.Flush()
cert := bw.String()
block, err := pemBlockForKey(priv)
if err != nil {
return "", "", errors.Wrap(errFailedPemKeyWrite, err)
}
if err := pem.Encode(buffKeyOut, block); err != nil {
return "", "", errors.Wrap(errFailedPemKeyWrite, err)
}
buffKeyOut.Flush()
key := keyOut.String()
return cert, key, nil
}
func publicKey(priv interface{}) (interface{}, error) {
if priv == nil {
return nil, errPrivateKeyEmpty
}
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey, nil
case *ecdsa.PrivateKey:
return &k.PublicKey, nil
default:
return nil, errPrivateKeyUnsupportedType
}
}
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
return nil, err
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
default:
return nil, nil
}
}
+125
View File
@@ -0,0 +1,125 @@
swagger: "2.0"
info:
title: Mainflux Certs service
description: HTTP API for Certs service
version: "1.0.0"
consumes:
- "application/json"
produces:
- "application/json"
paths:
/certs:
post:
consumes:
- application/json
summary: Creates a certificate for thing
description: Creates a certificate for thing
tags:
- Thing to proxy
parameters:
- $ref: "#/parameters/Authorization"
- in: body
name: cert
description:
schema:
type: object
required:
- thing_id
- days_valid
- rsa_bits
properties:
thing_id:
type: string
days_valid:
type: string
rsa_bits:
type: integer
responses:
201:
description: Created
400:
description: Failed due to malformed JSON.
500:
description: Unexpected server-side error ocurred.
/certs/{thingID}:
get:
summary: Retrieves certificates
description: |
Retrieves a certificates for given thing ID .
tags:
- configs
parameters:
- $ref: "#/parameters/ConfigAuth"
- $ref: "#/parameters/ThingID"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/CertsRes"
404:
description: |
Failed to retrieve corresponding certificate.
500:
$ref: "#/responses/ServiceError"
/certs/revoke:
delete:
summary: Revokes certificate
description: |
Revokes a certificates for given thing ID .
tags:
- configs
parameters:
- $ref: "#/parameters/ConfigAuth"
- $ref: "#/parameters/ThingID"
responses:
200:
description: Certificate revoked.
schema:
$ref: "#/definitions/RevokeRes"
404:
description: |
Failed to revoke corresponding certificate.
500:
$ref: "#/responses/ServiceError"
parameters:
Authorization:
name: Authorization
description: User's access token. Used instead of credentials in env or config.toml.
in: header
type: string
required: false
ThingID:
name: thingID
description: Thing ID
in: path
type: string
required: true
definitions:
CertsRes:
type: object
properties:
thing_id:
type: string
description: Corresponding Mainflux Thing ID.
client_cert:
type: string
description: Client Certificate.
client_key:
type: string
description: Key for the client_cert.
issuing_ca:
type: string
description:
serial:
type: string
description: Certificate serial
expire:
type: string
description: Certificate expiry date
RevokeRes:
type: object
properties:
revocation_time:
type: string
description: Certificate revocation time
+57
View File
@@ -0,0 +1,57 @@
package cli
import (
"errors"
"strconv"
"github.com/spf13/cobra"
)
var cmdCerts = []cobra.Command{
cobra.Command{
Use: "issue",
Short: "issue <thing_id> <keybits> <keytype> <hoursvalid> <user_auth_token>",
Long: `Issues new certificate for a thing`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 5 {
logUsage(cmd.Short)
return
}
thingID := args[0]
keyBits, err := strconv.Atoi(args[1])
if err != nil {
logError(errors.New("invalid format for keybits"))
return
}
keyType := args[2]
valid := args[3]
token := args[4]
c, err := sdk.IssueCert(thingID, keyBits, keyType, valid, token)
if err != nil {
logError(err)
return
}
logJSON(c)
},
},
}
// NewCertsCmd returns certificate command.
func NewCertsCmd() *cobra.Command {
cmd := cobra.Command{
Use: "cert",
Short: "Certificate management",
Long: `Certificate management: create certificates for things"`,
Run: func(cmd *cobra.Command, args []string) {
logUsage("cert issue <thing_id> <keybits> <keytype> <hoursvalid> <user_auth_token>")
},
}
for i := range cmdCerts {
cmd.AddCommand(&cmdCerts[i])
}
return &cmd
}
+414
View File
@@ -0,0 +1,414 @@
// SPDX-License-Identifier: Apache-2.0
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux"
authapi "github.com/mainflux/mainflux/authn/api/grpc"
"github.com/mainflux/mainflux/certs"
"github.com/mainflux/mainflux/certs/api"
vault "github.com/mainflux/mainflux/certs/pki"
"github.com/mainflux/mainflux/certs/postgres"
"github.com/mainflux/mainflux/logger"
"github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/jmoiron/sqlx"
mflog "github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
jconfig "github.com/uber/jaeger-client-go/config"
)
const (
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDB = "certs"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defClientTLS = "false"
defCACerts = ""
defPort = "8204"
defServerCert = ""
defServerKey = ""
defBaseURL = "http://localhost"
defThingsPrefix = ""
defJaegerURL = ""
defAuthnURL = "localhost:8181"
defAuthnTimeout = "1s"
defSignCAPath = "ca.crt"
defSignCAKeyPath = "ca.key"
defSignHoursValid = "2048h"
defSignRSABits = ""
defVaultHost = ""
defVaultRole = "mainflux"
defVaultToken = ""
defVaultPKIPath = "pki_int"
envPort = "MF_CERTS_HTTP_PORT"
envLogLevel = "MF_CERTS_LOG_LEVEL"
envDBHost = "MF_CERTS_DB_HOST"
envDBPort = "MF_CERTS_DB_PORT"
envDBUser = "MF_CERTS_DB_USER"
envDBPass = "MF_CERTS_DB_PASS"
envDB = "MF_CERTS_DB"
envDBSSLMode = "MF_CERTS_DB_SSL_MODE"
envDBSSLCert = "MF_CERTS_DB_SSL_CERT"
envDBSSLKey = "MF_CERTS_DB_SSL_KEY"
envDBSSLRootCert = "MF_CERTS_DB_SSL_ROOT_CERT"
envEncryptKey = "MF_CERTS_ENCRYPT_KEY"
envClientTLS = "MF_CERTS_CLIENT_TLS"
envCACerts = "MF_CERTS_CA_CERTS"
envServerCert = "MF_CERTS_SERVER_CERT"
envServerKey = "MF_CERTS_SERVER_KEY"
envBaseURL = "MF_SDK_BASE_URL"
envThingsPrefix = "MF_SDK_THINGS_PREFIX"
envJaegerURL = "MF_JAEGER_URL"
envAuthnURL = "MF_AUTHN_GRPC_URL"
envAuthnTimeout = "MF_AUTHN_GRPC_TIMEOUT"
envSignCAPath = "MF_CERTS_SIGN_CA_PATH"
envSignCAKey = "MF_CERTS_SIGN_CA_KEY_PATH"
envSignHoursValid = "MF_CERTS_SIGN_HOURS_VALID"
envSignRSABits = "MF_CERTS_SIGN_RSA_BITS"
envVaultHost = "MF_CERTS_VAULT_HOST"
envVaultPKIPath = "MF_CERTS_VAULT_PKI_PATH"
envVaultRole = "MF_CERTS_VAULT_ROLE"
envVaultToken = "MF_CERTS_VAULT_TOKEN"
)
var (
errFailedCertLoading = errors.New("failed to load certificate")
errFailedCertDecode = errors.New("failed to decode certificate")
errMissingCACertificate = errors.New("missing CA")
errPrivateKeyEmpty = errors.New("private key empty")
errPrivateKeyUnsupportedType = errors.New("private key unsupported type")
errCertsRemove = errors.New("failed to remove certificate")
errCACertificateDoesntExist = errors.New("CA certificate doesnt exist")
errCAKeyDoesntExist = errors.New("CA certificate key doesnt exist")
)
type config struct {
logLevel string
dbConfig postgres.Config
clientTLS bool
encKey []byte
caCerts string
httpPort string
serverCert string
serverKey string
baseURL string
thingsPrefix string
jaegerURL string
authnURL string
authnTimeout time.Duration
// Sign and issue certificates
// without 3rd party PKI
signCAPath string
signCAKeyPath string
signRSABits int
signHoursValid string
// 3rd party PKI API access settings
pkiPath string
pkiToken string
pkiHost string
pkiRole string
}
func main() {
cfg := loadConfig()
logger, err := mflog.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
tlsCert, caCert, err := loadCertificates(cfg)
if err != nil {
logger.Error("Failed to load CA certificates for issuing client certs")
}
pkiClient, err := vault.NewVaultClient(cfg.pkiToken, cfg.pkiHost, cfg.pkiPath, cfg.pkiRole)
if err != nil {
logger.Error("Failed to init vault client")
}
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
authTracer, authCloser := initJaeger("auth", cfg.jaegerURL, logger)
defer authCloser.Close()
authConn := connectToAuth(cfg, logger)
defer authConn.Close()
auth := authapi.NewClient(authTracer, authConn, cfg.authnTimeout)
svc := newService(auth, db, logger, nil, tlsCert, caCert, cfg, pkiClient)
errs := make(chan error, 2)
go startHTTPServer(svc, cfg, logger, errs)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("Certs service terminated: %s", err))
}
func loadConfig() config {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
tls = false
}
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDB, defDB),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
authnTimeout, err := time.ParseDuration(mainflux.Env(envAuthnTimeout, defAuthnTimeout))
if err != nil {
log.Fatalf("Invalid %s value: %s", envAuthnTimeout, err.Error())
}
signRSABits, err := strconv.Atoi(mainflux.Env(envSignRSABits, defSignRSABits))
if err != nil {
log.Fatalf("Invalid %s value: %s", envSignRSABits, err.Error())
}
return config{
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
httpPort: mainflux.Env(envPort, defPort),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
baseURL: mainflux.Env(envBaseURL, defBaseURL),
thingsPrefix: mainflux.Env(envThingsPrefix, defThingsPrefix),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
authnURL: mainflux.Env(envAuthnURL, defAuthnURL),
authnTimeout: authnTimeout,
signCAKeyPath: mainflux.Env(envSignCAKey, defSignCAKeyPath),
signCAPath: mainflux.Env(envSignCAPath, defSignCAPath),
signHoursValid: mainflux.Env(envSignHoursValid, defSignHoursValid),
signRSABits: signRSABits,
pkiToken: mainflux.Env(envVaultToken, defVaultToken),
pkiPath: mainflux.Env(envVaultPKIPath, defVaultPKIPath),
pkiRole: mainflux.Env(envVaultRole, defVaultRole),
pkiHost: mainflux.Env(envVaultHost, defVaultHost),
}
}
func connectToRedis(redisURL, redisPass, redisDB string, logger mflog.Logger) *redis.Client {
db, err := strconv.Atoi(redisDB)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to redis: %s", err))
os.Exit(1)
}
return redis.NewClient(&redis.Options{
Addr: redisURL,
Password: redisPass,
DB: db,
})
}
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
db, err := postgres.Connect(dbConfig)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
os.Exit(1)
}
return db
}
func connectToAuth(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to create tls credentials: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
opts = append(opts, grpc.WithInsecure())
logger.Info("gRPC communication is not encrypted")
}
conn, err := grpc.Dial(cfg.authnURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to authn service: %s", err))
os.Exit(1)
}
return conn
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(auth mainflux.AuthNServiceClient, db *sqlx.DB, logger mflog.Logger, esClient *redis.Client, tlsCert tls.Certificate, x509Cert *x509.Certificate, cfg config, pkiAgent vault.Agent) certs.Service {
certsRepo := postgres.NewRepository(db, logger)
certsConfig := certs.Config{
LogLevel: cfg.logLevel,
ClientTLS: cfg.clientTLS,
CaCerts: cfg.caCerts,
HTTPPort: cfg.httpPort,
ServerCert: cfg.serverCert,
ServerKey: cfg.serverKey,
BaseURL: cfg.baseURL,
ThingsPrefix: cfg.thingsPrefix,
JaegerURL: cfg.jaegerURL,
AuthnURL: cfg.authnURL,
AuthnTimeout: cfg.authnTimeout,
SignTLSCert: tlsCert,
SignX509Cert: x509Cert,
SignHoursValid: cfg.signHoursValid,
SignRSABits: cfg.signRSABits,
PKIToken: cfg.pkiToken,
PKIHost: cfg.pkiHost,
PKIPath: cfg.pkiPath,
PKIRole: cfg.pkiRole,
}
config := mfsdk.Config{
BaseURL: cfg.baseURL,
ThingsPrefix: cfg.thingsPrefix,
}
sdk := mfsdk.NewSDK(config)
svc := certs.New(auth, certsRepo, sdk, certsConfig, pkiAgent)
svc = api.NewLoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "certs",
Subsystem: "api",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "certs",
Subsystem: "api",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
return svc
}
func startHTTPServer(svc certs.Service, cfg config, logger mflog.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.httpPort)
if cfg.serverCert != "" || cfg.serverKey != "" {
logger.Info(fmt.Sprintf("Certs service started using https on port %s with cert %s key %s",
cfg.httpPort, cfg.serverCert, cfg.serverKey))
errs <- http.ListenAndServeTLS(p, cfg.serverCert, cfg.serverKey, api.MakeHandler(svc))
return
}
logger.Info(fmt.Sprintf("Certs service started using http on port %s", cfg.httpPort))
errs <- http.ListenAndServe(p, api.MakeHandler(svc))
}
func loadCertificates(conf config) (tls.Certificate, *x509.Certificate, error) {
var tlsCert tls.Certificate
var caCert *x509.Certificate
if conf.signCAPath == "" || conf.signCAKeyPath == "" {
return tlsCert, caCert, nil
}
if _, err := os.Stat(conf.signCAPath); os.IsNotExist(err) {
return tlsCert, caCert, errCACertificateDoesntExist
}
if _, err := os.Stat(conf.signCAKeyPath); os.IsNotExist(err) {
return tlsCert, caCert, errCAKeyDoesntExist
}
tlsCert, err := tls.LoadX509KeyPair(conf.signCAPath, conf.signCAKeyPath)
if err != nil {
return tlsCert, caCert, errors.Wrap(errFailedCertLoading, err)
}
b, err := ioutil.ReadFile(conf.signCAPath)
if err != nil {
return tlsCert, caCert, errors.Wrap(errFailedCertLoading, err)
}
block, _ := pem.Decode(b)
if block == nil {
log.Fatalf("No PEM data found, failed to decode CA")
}
caCert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return tlsCert, caCert, errors.Wrap(errFailedCertDecode, err)
}
return tlsCert, caCert, nil
}
+3
View File
@@ -17,6 +17,7 @@ func main() {
BaseURL: "http://localhost",
ReaderURL: "http://localhost:8905",
BootstrapURL: "http://localhost:8202",
CertsURL: "http://localhost:8204",
ReaderPrefix: "",
UsersPrefix: "",
ThingsPrefix: "",
@@ -44,6 +45,7 @@ func main() {
messagesCmd := cli.NewMessagesCmd()
provisionCmd := cli.NewProvisionCmd()
bootstrapCmd := cli.NewBootstrapCmd()
certsCmd := cli.NewCertsCmd()
// Root Commands
rootCmd.AddCommand(versionCmd)
@@ -53,6 +55,7 @@ func main() {
rootCmd.AddCommand(messagesCmd)
rootCmd.AddCommand(provisionCmd)
rootCmd.AddCommand(bootstrapCmd)
rootCmd.AddCommand(certsCmd)
// Root Flags
rootCmd.PersistentFlags().StringVarP(
+22 -8
View File
@@ -22,7 +22,6 @@ const (
defLogLevel = "debug"
defConfigFile = "config.toml"
defTLS = "false"
defCACerts = ""
defServerCert = ""
defServerKey = ""
defThingsLocation = "http://localhost"
@@ -34,17 +33,18 @@ const (
defMfAPIKey = ""
defMfBSURL = "http://localhost:8202/things/configs"
defMfWhiteListURL = "http://localhost:8202/things/state"
defMfCertsURL = "http://localhost/certs"
defMfCertsURL = "http://localhost:8204"
defProvisionCerts = "false"
defProvisionBS = "true"
defBSAutoWhitelist = "true"
defBSContent = ""
defCertsHoursValid = "2400h"
defCertsKeyBits = "4096"
envConfigFile = "MF_PROVISION_CONFIG_FILE"
envLogLevel = "MF_PROVISION_LOG_LEVEL"
envHTTPPort = "MF_PROVISION_HTTP_PORT"
envTLS = "MF_PROVISION_ENV_CLIENTS_TLS"
envCACerts = "MF_PROVISION_CA_CERTS"
envServerCert = "MF_PROVISION_SERVER_CERT"
envServerKey = "MF_PROVISION_SERVER_KEY"
envMQTTURL = "MF_PROVISION_MQTT_URL"
@@ -60,15 +60,18 @@ const (
envProvisionBS = "MF_PROVISION_BS_CONFIG_PROVISIONING"
envBSAutoWhiteList = "MF_PROVISION_BS_AUTO_WHITELIST"
envBSContent = "MF_PROVISION_BS_CONTENT"
envCertsHoursValid = "MF_PROVISION_CERTS_HOURS_VALID"
envCertsKeyBits = "MF_PROVISION_CERTS_RSA_BITS"
)
var (
errMissingConfigFile = errors.New("missing config file setting")
errFailedToLoadConfigFile = errors.New("failed to load config from file")
errFailedToGetAutoWhiteList = errors.New("failed to get auto whitelist setting")
errFailLoadingConfigFile = errors.New("failed to load config from file")
errFailGettingAutoWhiteList = errors.New("failed to get auto whitelist setting")
errFailGettingCertSettings = errors.New("failed to get certificate file setting")
errFailGettingTLSConf = errors.New("failed to get TLS setting")
errFailGettingProvBS = errors.New("failed to get BS url setting")
errFailSettingKeyBits = errors.New("failed to set rsa number of bits")
)
func main() {
@@ -135,7 +138,7 @@ func loadConfigFromFile(file string) (provision.Config, error) {
}
c, err := provision.Read(file)
if err != nil {
return provision.Config{}, errors.Wrap(errFailedToLoadConfigFile, err)
return provision.Config{}, errors.Wrap(errFailLoadingConfigFile, err)
}
return c, nil
}
@@ -156,16 +159,19 @@ func loadConfig() (provision.Config, error) {
autoWhiteList, err := strconv.ParseBool(mainflux.Env(envBSAutoWhiteList, defBSAutoWhitelist))
if err != nil {
return provision.Config{}, errors.Wrap(errFailedToGetAutoWhiteList, fmt.Errorf(" for %s", envBSAutoWhiteList))
return provision.Config{}, errors.Wrap(errFailGettingAutoWhiteList, fmt.Errorf(" for %s", envBSAutoWhiteList))
}
if autoWhiteList && !provisionBS {
return provision.Config{}, errors.New("Can't auto whitelist if auto config save is off")
}
keyBits, err := strconv.Atoi(mainflux.Env(envCertsKeyBits, defCertsKeyBits))
if err != nil && provisionX509 == true {
return provision.Config{}, errFailSettingKeyBits
}
cfg := provision.Config{
Server: provision.ServiceConf{
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
CACerts: mainflux.Env(envCACerts, defCACerts),
ServerCert: mainflux.Env(envServerCert, defServerCert),
ServerKey: mainflux.Env(envServerKey, defServerKey),
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
@@ -179,6 +185,10 @@ func loadConfig() (provision.Config, error) {
UsersLocation: mainflux.Env(envUsersLocation, defUsersLocation),
TLS: tls,
},
Certs: provision.Certs{
HoursValid: mainflux.Env(envCertsHoursValid, defCertsHoursValid),
KeyBits: keyBits,
},
Bootstrap: provision.Bootstrap{
X509Provision: provisionX509,
Provision: provisionBS,
@@ -227,6 +237,10 @@ func mergeConfigs(dst, src interface{}) interface{} {
if dField.Interface() == false {
dField.Set(reflect.ValueOf(sField.Interface()))
}
case reflect.Int:
if dField.Interface() == 0 {
dField.Set(reflect.ValueOf(sField.Interface()))
}
case reflect.String:
if dField.Interface() == "" {
dField.Set(reflect.ValueOf(sField.Interface()))
+76
View File
@@ -0,0 +1,76 @@
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional certs services. Since it's optional, this file is
# dependent of docker-compose file from <project_root>/docker. In order to run this services, execute command:
# docker-compose -f docker/docker-compose.yml -f docker/addons/certs/docker-compose.yml up
# from project root.
version: "3.7"
networks:
docker_mainflux-base-net:
external: true
volumes:
mainflux-certs-db-volume:
services:
certs-db:
image: postgres:10.2-alpine
container_name: mainflux-certs-db
restart: on-failure
environment:
POSTGRES_USER: ${MF_CERTS_DB_USER}
POSTGRES_PASSWORD: ${MF_CERTS_DB_PASS}
POSTGRES_DB: ${MF_CERTS_DB}
networks:
- docker_mainflux-base-net
volumes:
- mainflux-certs-db-volume:/var/lib/postgresql/data
certs:
image: mainflux/certs:latest
container_name: mainflux-certs
depends_on:
- certs-db
restart: on-failure
networks:
- docker_mainflux-base-net
ports:
- ${MF_CERTS_HTTP_PORT}:${MF_CERTS_HTTP_PORT}
environment:
MF_CERTS_LOG_LEVEL: ${MF_CERTS_LOG_LEVEL}
MF_CERTS_DB_HOST: certs-db
MF_CERTS_DB_PORT: ${MF_CERTS_DB_PORT}
MF_CERTS_DB_PASS: ${MF_CERTS_DB_PASS}
MF_CERTS_DB_USER: ${MF_CERTS_DB_USER}
MF_CERTS_DB: ${MF_CERTS_DB}
MF_CERTS_DB_SSL_MODE: ${MF_CERTS_DB_SSL_MODE}
MF_CERTS_DB_SSL_CERT: ${MF_CERTS_DB_SSL_CERT}
MF_CERTS_DB_SSL_KEY: ${MF_CERTS_DB_SSL_KEY}
MF_CERTS_DB_SSL_ROOT_CERT: ${MF_CERTS_DB_SSL_ROOT_CERT}
MF_CERTS_ENCRYPT_KEY: ${MF_CERTS_ENCRYPT_KEY}
MF_CERTS_CLIENT_TLS: ${MF_CERTS_CLIENT_TLS}
MF_CERTS_CA_CERTS: ${MF_CERTS_CA_CERTS}
MF_CERTS_HTTP_PORT: ${MF_CERTS_HTTP_PORT}
MF_CERTS_SERVER_CERT: ${MF_CERTS_SERVER_CERT}
MF_CERTS_SERVER_KEY: ${MF_CERTS_SERVER_KEY}
MF_SDK_BASE_URL: ${MF_SDK_BASE_URL}
MF_SDK_THINGS_PREFIX: ${MF_SDK_THINGS_PREFIX}
MF_JAEGER_URL: ${MF_JAEGER_URL}
MF_AUTHN_GRPC_URL: ${MF_AUTHN_GRPC_URL}
MF_AUTHN_GRPC_TIMEOUT: ${MF_AUTHN_GRPC_TIMEOUT}
MF_CERTS_SIGN_CA_PATH: ${MF_CERTS_SIGN_CA_PATH}
MF_CERTS_SIGN_CA_KEY_PATH: ${MF_CERTS_SIGN_CA_KEY_PATH}
MF_CERTS_SIGN_HOURS_VALID: ${MF_CERTS_SIGN_HOURS_VALID}
MF_CERTS_SIGN_RSA_BITS: ${MF_CERTS_SIGN_RSA_BITS}
MF_CERTS_VAULT_TOKEN: ${MF_CERTS_VAULT_TOKEN}
MF_CERTS_VAULT_HOST: ${MF_CERTS_VAULT_HOST}
MF_CERTS_VAULT_PKI_PATH: ${MF_CERTS_VAULT_PKI_PATH}
MF_CERTS_VAULT_ROLE: ${MF_CERTS_VAULT_ROLE}
volumes:
- ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key
- ../../ssl/certs/ca.crt:/etc/ssl/certs/ca.crt
+4 -1
View File
@@ -27,7 +27,6 @@ services:
MF_PROVISION_CONFIG_FILE: ${MF_PROVISION_CONFIG_FILE}
MF_PROVISION_LOG_LEVEL: ${MF_PROVISION_LOG_LEVEL}
MF_PROVISION_ENV_CLIENTS_TLS: ${MF_PROVISION_ENV_CLIENTS_TLS}
MF_PROVISION_CA_CERTS: ${MF_PROVISION_CA_CERTS}
MF_PROVISION_SERVER_CERT: ${MF_PROVISION_SERVER_CERT}
MF_PROVISION_SERVER_KEY: ${MF_PROVISION_SERVER_KEY}
MF_PROVISION_MQTT_URL: ${MF_PROVISION_MQTT_URL}
@@ -43,5 +42,9 @@ services:
MF_PROVISION_BS_CONFIG_PROVISIONING: ${MF_PROVISION_BS_CONFIG_PROVISIONING}
MF_PROVISION_BS_AUTO_WHITELIST: ${MF_PROVISION_BS_AUTO_WHITELIST}
MF_PROVISION_BS_CONTENT: ${MF_PROVISION_BS_CONTENT}
MF_PROVISION_CERTS_RSA_BITS: ${MF_PROVISION_CERTS_RSA_BITS}
MF_PROVISION_CERTS_HOURS_VALID: ${MF_PROVISION_CERTS_HOURS_VALID}
volumes:
- ./configs:/configs
- ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key
- ../../ssl/certs/ca.crt:/etc/ssl/certs/ca.crt
+4 -1
View File
@@ -16,13 +16,16 @@ require (
github.com/gofrs/uuid v3.3.0+incompatible
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.4.2
github.com/golangci/golangci-lint v1.27.0 // indirect
github.com/gopcua/opcua v0.1.6
github.com/hashicorp/vault/api v1.0.4
github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e
github.com/influxdata/influxdb v1.8.1
github.com/jmoiron/sqlx v1.2.1-0.20190319043955-cdf62fdf55f6
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5
github.com/lib/pq v1.7.0
github.com/mainflux/mproxy v0.2.1
github.com/mainflux/senml v1.5.0
github.com/mitchellh/mapstructure v1.1.2
github.com/nats-io/nats.go v1.10.0
github.com/opentracing/opentracing-go v1.2.0
github.com/ory/dockertest/v3 v3.6.0
+207
View File
@@ -25,6 +25,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157 h1:hY39LwQHh+1kaovmIjOrlqnXNX6tygSRfLkkK33IkZU=
github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Microsoft/go-winio v0.4.7 h1:vOvDiY/F1avSWlCWiKJjdYKz2jVjTK3pWPHndeG4OAY=
github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
@@ -33,8 +35,11 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
@@ -71,6 +76,8 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bombsimon/wsl/v3 v3.0.0 h1:w9f49xQatuaeTJFaNP4SpiWSR5vfT6IstPtM62JjcqA=
github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY=
@@ -111,6 +118,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
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=
@@ -150,6 +158,7 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
@@ -162,6 +171,8 @@ github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-critic/go-critic v0.4.1 h1:4DTQfT1wWwLg/hzxwD9bkdhDQrdJtxe6DUTadPlrIeE=
github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
@@ -170,11 +181,15 @@ github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-redis/redis v6.15.0+incompatible h1:/Wib9cA7CF3SQxBZRMHyQvqzlwzc8PJGDMkRfqQebSE=
github.com/go-redis/redis v6.15.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o=
@@ -185,6 +200,28 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZp
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ=
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA=
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/go-zoo/bone v1.3.0 h1:PY6sHq37FnQhj+4ZyqFIzJQHvrrGx0GEc3vTZZC/OsI=
github.com/go-zoo/bone v1.3.0/go.mod h1:HI3Lhb7G3UQcAwEhOJ2WyNcsFtQX1WYHa0Hl4OBbhW8=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
@@ -224,6 +261,8 @@ github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWS
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gocql/gocql v0.0.0-20181106112037-68ae1e384be4 h1:n5NlV76GU6337XT+jarynqONI5LlqaYkTPaFZ25og6g=
github.com/gocql/gocql v0.0.0-20181106112037-68ae1e384be4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7 h1:TvUE5vjfoa7fFHMlmGOk0CsauNj1w4yJjR9+/GnWVCw=
@@ -231,6 +270,8 @@ github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7/go.mod h1:DL0ekTmBSTdl
github.com/gocql/gocql v0.0.0-20200624222514-34081eda590e h1:SroDcndcOU9BVAduPf/PXihXoR2ZYTQYLXbupbqxAyQ=
github.com/gocql/gocql v0.0.0-20200624222514-34081eda590e/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg=
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw=
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
@@ -275,6 +316,36 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8=
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8=
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.27.0 h1:VYLx63qb+XJsHdZ27PMS2w5JZacN0XG8ffUwe7yQomo=
github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
@@ -294,6 +365,7 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gopcua/opcua v0.1.6 h1:B9SVRKQGzcWcwP2QPYN93Uku32+3wL+v5cgzBxE6V5I=
github.com/gopcua/opcua v0.1.6/go.mod h1:INwnDoRxmNWAt7+tzqxuGqQkSF2c1C69VAL0c2q6AcY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -311,6 +383,8 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -324,18 +398,33 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
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 h1:z3ollgGRg8RjfJH6UVBaG54R70GFd++QOkvnJH3VSBY=
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-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
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.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
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.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
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-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
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-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -346,6 +435,13 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/vault v1.4.3 h1:5o6Bfa9loNr9qfUiw6SI6AnOhEhNNCpNhSWkYRjq7nQ=
github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
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/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe h1:MCgzztuoH5LZNr9AkIaicIDvCfACu11KUCCZQnRHDC0=
github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e h1:0aewS5NTyxftZHSnFaJmWE5oCCrj4DyEXkAiMa1iZJM=
@@ -382,9 +478,15 @@ github.com/jcmturner/gokrb5/v8 v8.2.0 h1:lzPl/30ZLkTveYsYZPKMcgXc8MbnE6RsTd4F9Kg
github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM=
github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0=
github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4=
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.1-0.20190319043955-cdf62fdf55f6 h1:6KHlj0TRbiafVOoCXCixS0GbdWF/paU373ABxEzhY6s=
github.com/jmoiron/sqlx v1.2.1-0.20190319043955-cdf62fdf55f6/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 h1:lrdPtrORjGv1HbbEvKWDUAy97mPpFm4B8hp77tcCUJY=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -406,11 +508,15 @@ github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0Lh
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
@@ -424,6 +530,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -434,6 +541,7 @@ github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
@@ -459,8 +567,12 @@ github.com/mainflux/senml v1.0.1 h1:qWKIGeUe7YEygM3xZcJ9Lbq+DHuT8V23dz1hgAYkYEY=
github.com/mainflux/senml v1.0.1/go.mod h1:SMX76mM5yenjLVjZOM27+njCGkP+AA64O46nRQiBRlE=
github.com/mainflux/senml v1.5.0 h1:GAd1y1eMohfa6sVYcr2iQfVfkkh9l/q7B1TWF5L68xs=
github.com/mainflux/senml v1.5.0/go.mod h1:SMX76mM5yenjLVjZOM27+njCGkP+AA64O46nRQiBRlE=
github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ=
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
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-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
@@ -481,25 +593,35 @@ github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO
github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do=
github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
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.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
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/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakabonne/nestif v0.3.0 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaPw=
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
github.com/nats-io/go-nats v1.6.0 h1:FznPwMfrVwGnSCh7JTXyJDRW0TIkD4Tr+M1LPJt9T70=
github.com/nats-io/go-nats v1.6.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
@@ -523,6 +645,9 @@ github.com/nats-io/nuid v1.0.0 h1:44QGdhbiANq8ZCbUkdn6W5bqtg+mHuDE4wOUuxxndFs=
github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@@ -533,9 +658,11 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -576,8 +703,10 @@ github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMF
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -631,6 +760,7 @@ github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -649,13 +779,24 @@ github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.0.4 h1:oCreMAt9GuFXDe9jW4HBpc3GjdX3R/sUEcLAGh1zPx8=
github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
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/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/securego/gosec/v2 v2.3.0 h1:y/9mCF2WPDbSDpL3QDWZD3HHGrSYw0QSHnCqTfs4JPE=
github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME=
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -671,6 +812,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/sony/gobreaker v0.0.0-20180905101324-b2a34562d02c h1:7EMc5KMRVlkzEyK5n4YqdPEsmO+6AlAGCJiqnqW6n2Y=
github.com/sony/gobreaker v0.0.0-20180905101324-b2a34562d02c/go.mod h1:XvpJiTD8NibaH7z0NzyfhR1+NQDtR9F/x92xheTwC9k=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs=
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
@@ -689,10 +832,13 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -715,11 +861,19 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
github.com/tetafro/godot v0.3.7 h1:+mecr7RKrUKB5UQ1gwqEMn13sDKTyDR8KNIquB9mm+8=
github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/uber/jaeger-client-go v2.16.0+incompatible h1:Q2Pp6v3QYiocMxomCaJuwQGFt7E53bPYqEgug/AoBtY=
github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-client-go v2.22.1+incompatible h1:NHcubEkVbahf9t3p75TOCR83gdUHXjRJvjoBh1yACsM=
@@ -733,8 +887,18 @@ github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo=
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs=
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
@@ -746,6 +910,8 @@ github.com/xdg/stringprep v1.0.1-0.20180714160509-73f8eece6fdc/go.mod h1:Jhud4/s
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -827,9 +993,12 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -880,6 +1049,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -899,6 +1069,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qq
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -917,6 +1088,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
@@ -924,24 +1096,31 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
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-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -949,22 +1128,33 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200108203644-89082a384178 h1:f5gMxb6FbpY48csegk9UPd7IAHVrBD013CU7N4pWzoE=
golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 h1:M9Fif0OxNji8w+HvmhVQ8KJtiZOsjU9RgslJGhn95XE=
golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@@ -1001,6 +1191,7 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1022,12 +1213,14 @@ google.golang.org/genproto v0.0.0-20200603110839-e855014d5736 h1:+IE3xTD+6Eb7QWG
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
@@ -1055,11 +1248,13 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@@ -1083,6 +1278,8 @@ gopkg.in/ory-am/dockertest.v3 v3.3.2/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKek
gopkg.in/ory/dockertest.v3 v3.3.5 h1:bm2RXztqdTSinb1tUP9/iFTPmhy3sk2EL2k9GSMKNEE=
gopkg.in/ory/dockertest.v3 v3.3.5/go.mod h1:wI78nwA6jQZVXv3va0CcbJAuftRnAa063zO5Fek7+uI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
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=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
@@ -1111,7 +1308,17 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
+20 -8
View File
@@ -8,29 +8,34 @@ import (
"net/http"
)
const certsEndpoint = "certs"
// Cert represents certs data.
type Cert struct {
CACert string `json:"ca_cert,omitempty"`
CACert string `json:"issuing_ca,omitempty"`
ClientKey string `json:"client_key,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
}
func (sdk mfSDK) Cert(thingID, thingKey, token string) (Cert, error) {
func (sdk mfSDK) IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, error) {
var c Cert
r := certReq{
ThingID: thingID,
ThingKey: thingKey,
ThingID: thingID,
KeyBits: keyBits,
KeyType: keyType,
Valid: valid,
}
d, err := json.Marshal(r)
if err != nil {
return Cert{}, err
}
res, err := request(http.MethodPost, token, sdk.certsURL, d)
url := createURL(sdk.certsURL, sdk.certsPrefix, certsEndpoint)
res, err := request(http.MethodPost, token, url, d)
if err != nil {
return Cert{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
if res.StatusCode != http.StatusOK {
return Cert{}, ErrCerts
}
body, err := ioutil.ReadAll(res.Body)
@@ -62,6 +67,10 @@ func (sdk mfSDK) RemoveCert(id, token string) error {
}
}
func (sdk mfSDK) RevokeCert(thingID, certID string, token string) error {
panic("not implemented")
}
func request(method, jwt, url string, data []byte) (*http.Response, error) {
req, err := http.NewRequest(method, url, bytes.NewReader(data))
if err != nil {
@@ -79,6 +88,9 @@ func request(method, jwt, url string, data []byte) (*http.Response, error) {
}
type certReq struct {
ThingID string `json:"thing_id,omitempty"`
ThingKey string `json:"thing_key,omitempty"`
ThingID string `json:"thing_id"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
Encryption string `json:"encryption"`
Valid string `json:"valid"`
}
+1 -1
View File
@@ -79,7 +79,7 @@ func (sdk mfSDK) ReadMessages(chanName, token string) (MessagesPage, error) {
return mp, nil
}
func (sdk *mfSDK) SetContentType(ct ContentType) error {
func (sdk mfSDK) SetContentType(ct ContentType) error {
if ct != CTJSON && ct != CTJSONSenML && ct != CTBinary {
return ErrInvalidContentType
}
+7 -3
View File
@@ -193,11 +193,14 @@ type SDK interface {
// Whitelist updates Thing state Config with given ID belonging to the user identified by the given token.
Whitelist(token string, cfg BootstrapConfig) error
// Cert issues a certificate for a thing required for mtls.
Cert(thingID, thingKey, token string) (Cert, error)
// IssueCert issues a certificate for a thing required for mtls.
IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, error)
// RemoveCert remove a certificate
// RemoveCert removes a certificate
RemoveCert(id, token string) error
// RevokeCert revokes certificate with certID for thing with thingID
RevokeCert(thingID, certID, token string) error
}
type mfSDK struct {
@@ -208,6 +211,7 @@ type mfSDK struct {
readerPrefix string
usersPrefix string
thingsPrefix string
certsPrefix string
channelsPrefix string
httpAdapterPrefix string
bootstrapPrefix string
+19 -2
View File
@@ -25,7 +25,6 @@ default values.
| MF_PROVISION_CONFIG_FILE | Provision config file | config.toml |
| MF_PROVISION_HTTP_PORT | Provision service listening port | 8091 |
| MF_PROVISION_ENV_CLIENTS_TLS | Mainflux SDK TLS verification | false |
| MF_PROVISION_CA_CERTS | Mainflux gRPC secure certs | |
| MF_PROVISION_SERVER_CERT | Mainflux gRPC secure server cert | |
| MF_PROVISION_SERVER_KEY | Mainflux gRPC secure server key | |
| MF_PROVISION_SERVER_KEY | Mainflux gRPC secure server key | |
@@ -41,8 +40,12 @@ default values.
| MF_PROVISION_CERTS_SVC_URL | Certificats service URL | http://localhost/certs |
| MF_PROVISION_X509_PROVISIONING | Should X509 client cert be provisioned | false |
| MF_PROVISION_BS_CONFIG_PROVISIONING | Should thing config be saved in Bootstrap service | true |
| MF_PROVISION_BS_AUTO_WHITEIST | Should thing be auto whitelisted | true |
| MF_PROVISION_BS_AUTO_WHITELIST | Should thing be auto whitelisted | true |
| MF_PROVISION_BS_CONTENT | Bootstrap service content | {} |
| MF_PROVISION_CERTS_CA | Mainflux CA cert for generating certificates | "" |
| MF_PROVISION_CERTS_CA_KEY | Mainflux CA cert private key | "" |
| MF_PROVISION_CERTS_RSA_BITS | Certificate RSA bits parameter | 4096 |
| MF_PROVISION_CERTS_HOURS_VALID | Number of days that certificate is valid | "2400h" |
By default, call to `/mapping` endpoint will create one thing and two channels (`control` and `data`) and connect it. If there is a requirement for different provision layout we can use [config](docker/configs/config.toml) file in addition to environment variables.
@@ -161,6 +164,20 @@ Response contains created things, channels and certificates if any:
}
```
## Certificates
Provision service has `/certs` endpoint that can be used to generate certificates for things when mTLS is required:
- `users_token` - users authentication token or API token
- `thing_id` - id of the thing for which certificate is going to be generated
```bash
curl -s -X POST http://localhost:8190/certs -H "Authorization: <users_token>" -H 'Content-Type: application/json' -d '{"thing_id": "<thing_id>", "rsa_bits":4096, "days_valid":"2400h" }'
```
```json
{
"thing_cert": "-----BEGIN CERTIFICATE-----\nMIIEmDCCA4CgAwIBAgIQCZ0NOq2oKLo+XftbAu0TfzANBgkqhkiG9w0BAQsFADBX\nMRIwEAYDVQQDDAlsb2NhbGhvc3QxETAPBgNVBAoMCE1haW5mbHV4MQwwCgYDVQQL\nDANJb1QxIDAeBgkqhkiG9w0BCQEWEWluZm9AbWFpbmZsdXguY29tMB4XDTIwMDYw\nNTEyMzc1M1oXDTIwMDkxMzEyMzc1M1owVTERMA8GA1UEChMITWFpbmZsdXgxETAP\nBgNVBAsTCG1haW5mbHV4MS0wKwYDVQQDEyQyYmZlYmZmMC05ODZhLTQ3ZTAtOGQ3\nYS00YTRiN2UyYjU3OGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn\nWvTuOIdhqOLEREcEJqfQAtDoYu3rUDijOffXuWFZgNqfZTGmoD5ZqJXxwbZ4tCST\npdSteHtyr7JXnPJQN1dsslU+q3haKjFoZRc39/7u4/8XCTwlqbMl9YVcwqS+FLkM\niLSyyqzryP7Y8H8cidTKg56p5JALaEKfzZS6Km3G+CCinR6hNNW9ckWsy29a0/9E\nMAUtM+Lsk5OjsHzOnWruuqHsCx4ODI5aJQaMC1qntkbXkht0WDiwAt9SDQ3uLWru\nAoSJDK9a6EgR3a0Jf7ZiVPiwlZNjrB/I5OQyFDGqcmSAl2rdJqPkmaDXKKFyL1cG\nMIyHv62QzJoMdRoXu20lxyGxAvEjQNVHux4LA3dbf/85nEVTI2uP8crMf2Jnzbg5\n9zF+iTMJGpUlatCyK2RJS/mvHbbUIf5Ro3VbcPHbgFroJ7qMFz0Fc5kYY8IdwXjG\nlyG9MobKEO2CfBGRjPmCuTQq2HcuOy7F6KfQf3HToI8MmC5hBtCmTNbV8I3GIjWA\n/xJQLm2pVZ41QhrnNGtuqAYoe3Zt6OldxGRcoAj7KlIpYcPZ55PJ6mWcV6dB9Fnl\n5mYOwQL8jtfybbGWvqJldhTxUqm7/EbAaF0Qjmh4oOHMl2xADrmYzJHvf0llwr6g\noRQuzqxPi0aW3tkFNsm63NX1Ab5BXFQhMSj5+82blwIDAQABo2IwYDAOBgNVHQ8B\nAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQH\nBAUBAgMEBjAfBgNVHSMEGDAWgBRs4xR91qEjNRGmw391xS7x6Tc+8jANBgkqhkiG\n9w0BAQsFAAOCAQEAphLT8PjawRRWswU1B5oWnnqeTllnvGB88sjDPLAG0UiBlDLX\nwoPiBVPWuYV+MMJuaREgheYF1Ahx4Jrfy9stFDU7B99ON1T58oM1aKEq4rKc+/Ke\nyxrAFTonclC0LNaaOvpZZjsPFWr2muTQO8XHiS8icw3BLxEzoF+5aJ8ihtxRtfKL\nUvtHDqC6IPAbSUcvqyjrFh3RrTUAyGOzW12IEWSXP9DLwoiLPwJ6kCVoXdG/asjz\nUpk/jj7AUn9oJNF8nUbyhdOnmeJ2z0x1ylgYrIAxvGzm8zs+NEVN67CrBYKwstlN\nvw7DRQsCvGJjZzWj28VV3FGLtXFgu52bFZNBww==\n-----END CERTIFICATE-----\n",
"thing_cert_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIJJwIBAAKCAgEAp1r07jiHYajixERHBCan0ALQ6GLt61A4ozn317lhWYDan2Ux\npqA+WaiV8cG2eLQkk6XUrXh7cq+yV5zyUDdXbLJVPqt4WioxaGUXN/f+7uP/Fwk8\nJamzJfWFXMKkvhS5DIi0ssqs68j+2PB/HInUyoOeqeSQC2hCn82Uuiptxvggop0e\noTTVvXJFrMtvWtP/RDAFLTPi7JOTo7B8zp1q7rqh7AseDgyOWiUGjAtap7ZG15Ib\ndFg4sALfUg0N7i1q7gKEiQyvWuhIEd2tCX+2YlT4sJWTY6wfyOTkMhQxqnJkgJdq\n3Saj5Jmg1yihci9XBjCMh7+tkMyaDHUaF7ttJcchsQLxI0DVR7seCwN3W3//OZxF\nUyNrj/HKzH9iZ824OfcxfokzCRqVJWrQsitkSUv5rx221CH+UaN1W3Dx24Ba6Ce6\njBc9BXOZGGPCHcF4xpchvTKGyhDtgnwRkYz5grk0Kth3Ljsuxein0H9x06CPDJgu\nYQbQpkzW1fCNxiI1gP8SUC5tqVWeNUIa5zRrbqgGKHt2bejpXcRkXKAI+ypSKWHD\n2eeTyeplnFenQfRZ5eZmDsEC/I7X8m2xlr6iZXYU8VKpu/xGwGhdEI5oeKDhzJds\nQA65mMyR739JZcK+oKEULs6sT4tGlt7ZBTbJutzV9QG+QVxUITEo+fvNm5cCAwEA\nAQKCAgAmCIfNc89gpG8Ux6eUC+zrWxh7F7CWX97fSZdH0XuMSbplqyvDgHtrCOM6\n1BlSCS6e13skCVOU1tUjECoJjOoza7vvyCxL4XblEMRcFeI8DFi2tYST0qNCJzAt\nypaCFFeRv6fBUkpGM6GnT9Czfad8drkiRy1tSj6J7sC0JlxYcZ+JFUgWvtksesHW\n6UzfSXqj1n32reoOdeOBueRDWIcqxgNyj3w/GR9o4S1BunrZzpT+/Nd8c2g+qAh0\nrz7ROEUq3iucseNQN6XZWZWvqPScGE+EYhni9wUqNMqfjvNSlzi7+K1yoQtyMm/Z\nNgSq3JNcdsAZQbiCRd1ko2BQsGm3ZBnbsAJ1Dxcn+i9nF5DT/ddWjUWin6LYWuUM\n/0Bqfv3etlrFuP6yxc8bPEMX0ucJg4yVxdkDrm1tYlJ+ANEQoOlZqhngvjz0f8uO\nOtEcDLmiG5VG6Yl72UtWIw+ALnKc5U7ib43Qve0bDAKR5zlHODcRetN9BCMvpekY\nOA4hohkllTP25xmMzLokBqY9n38zEt74kJOp67VKMvhoF7QkrLOfKWCRJjFL7/9I\nHDa6jb31INA9Wu+p/2LIa6I1SUYnMvCUqISgF2hBG9Q9S9TZvKnYUvfurhFS9jZv\n18sxW7IFYWmQyioo+gsAmfKLolJtLl9hCmTfYi7oqCh/EtZdIQKCAQEA0Umkp0Uu\nimVilLjgYGTWLcg8T3NWaELQzb2HYRXSzEq/M8GOtEr7TR7noJBm8fcgl55HEnPl\ni4cEJrr+VprzGbdMtXjHbCD+I945GA6vv3khg7mbqS9a1Uw6gjrQEZgZQU+/IVCu\n9Pbvx8Af32xaBWuN2cFzC7Z6iB815LPc2O5qyZ3+3nEUPah+Z+a9WEeTR6M0hy5c\nkkaRqhehugHDgqMRWGt8GfsFOmaR13kvfFfKadPRPkaGkftCSKBMWjrU4uX7aulm\nD7k4VDbnXIBMhI039+0znSkhZdcV1zk6qwBYn9TtZ11PTlspFPjtPxqS5M6IGflw\nsXkZGv4rZ5CkiQKCAQEAzLVdw2qw/8rWGsCV39EKp7hXLvp7+FuodPvX1L55lWB0\nvmSOldGcNvb2ZsK3RNvgteb8VfKRgaY6waeN5Qm1UXazsOX4F+GThPGHstdNuzkt\nJofRQQHQVR3npZbCngSkSZdahQ9SjiLIDKn8baPN8I8HfpJ4oHLUvkayavbch1kJ\nYWUfGtVKxHGX5m/nnxLdgbJEx9Q+3Qa7DDHuxTqsEqhkk0R0Ganred34HjpDNMs6\nV95HFNolW3yKfuHETKA1bLhej+XdMa11Ts5hBVGCMnnT07WcGhxtyK2dSa656SyT\ngT9+Hd1VWZ/KPpAkQmH9boOr2ihE+oAXiZ4D1t53HwKCAQAD0cA7fTu4Mtl1tVoC\n6FQwSbMwD/7HsFB3MLpDv041hDexDhs4lxW29pVrjLcUO1pQ6gaKA6twvGoK+uah\nVfqRwZKYzTd2dbOtm+SW183FRMSjzsNUdxTFR7rZnZEmgQwU8Quf5AUNW2RM1Oi/\n/w41gxz3mFwtHotl6IvnPJEPNGqme0enb5Da/zQvWTqjXcsGR6gxv1rZIIiP/hZp\nepbCz48FehCtuLMDudN3hzKipkd/Xuo2pLrX9ynigWpjSyePbHsGHHRMXSj2AHqA\naab71EftMlr6x0FgxmgToWu8qyjy4cPjWwSTfX5mb5SEzktX+ZzqPG8eDgOzRmgs\nX6thAoIBADL3kQG/hZQaL1Z3zpjsFggOKH7E1KrQP0/pCCKqzeC4JDjnFm0MxCUX\nNd/96N1XFUqU2QyZGUs7VPO0QOrekOtYb4LCrxNbEXyPGicX3f2YTbqDJEFYL0OR\n74PV1ly7cR/1dA8e8oH6/O3SQMwXdYXIRqhn1Wq1TGyXc4KYNe3o6CH8qFLo+fWR\nBq3T/MopS0coWGGcYY5sR5PQts8aPY9jp67W40UkfkFYV5dHEEaLttn7uJzjd1ug\n1Waj1VjypnqMKNcQ9xKQSl21mohVc+IXXPsgA16o51iIiVm4DAeXFp6ebUsIOWDY\nHOWYw75XYV7rn5TwY8Qusi2MTw5nUycCggEAB/45U0LW7ZGpks/aF/BeGaSWiLIG\nodBWUjRQ4w+Le/pTC8Ci9fiidxuCDH6TQbsUTGKOk7GsfncWHTQJogaMyO26IJ1N\nmYGgK2JJvs7PKyIkocPDVD/Yh0gIzQIE92ZdyXUT21pIYKDUB9e3p0fy/+E0pyeI\nsmsV8oaLr4tZRY1cMogI+pvtUUferbLQmZHhFd9X3m3RslR43Dl1qpYQyzE3x/a3\nWA2NJZbJhh+LiAKzqk7swXOqrTrmXuzLcjMG+T/3lizrbLLuKjQrf+eehlpw0db0\nHVVvkMLOP5ZH/ImkmvOZJY7xxup89VV7LD7TfMKwXafOrjMDdvTAYPtgxw==\n-----END RSA PRIVATE KEY-----\n"
}
```
[mainflux]: https://github.com/mainflux/mainflux
[bootstrap]: https://github.com/mainflux/mainflux/tree/master/bootstrap
[export]: https://github.com/mainflux/export
+1 -1
View File
@@ -16,7 +16,7 @@ func doProvision(svc provision.Service) endpoint.Endpoint {
}
token := req.token
res, err := svc.Provision(req.Name, token, req.ExternalID, req.ExternalKey)
res, err := svc.Provision(token, req.Name, req.ExternalID, req.ExternalKey)
if err != nil {
return provisionRes{Error: err.Error()}, nil
+15 -2
View File
@@ -20,7 +20,7 @@ func NewLoggingMiddleware(svc provision.Service, logger log.Logger) provision.Se
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Provision(name, token, externalID, externalKey string) (res provision.Result, err error) {
func (lm *loggingMiddleware) Provision(token, name, externalID, externalKey string) (res provision.Result, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method provision for token: %s and things: %v took %s to complete", token, res.Things, time.Since(begin))
if err != nil {
@@ -30,5 +30,18 @@ func (lm *loggingMiddleware) Provision(name, token, externalID, externalKey stri
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Provision(name, token, externalID, externalKey)
return lm.svc.Provision(token, name, externalID, externalKey)
}
func (lm *loggingMiddleware) Cert(token, thingID, duration string, keyBits int) (cert string, key string, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method cert for token: %s and thing: %v took %s to complete", token, thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Cert(token, thingID, duration, keyBits)
}
+1
View File
@@ -43,6 +43,7 @@ func MakeHandler(svc provision.Service) http.Handler {
))
r.Handle("/metrics", promhttp.Handler())
r.GetFunc("/version", mainflux.Version("provision"))
return r
}
+7 -1
View File
@@ -16,7 +16,6 @@ type ServiceConf struct {
Port string `toml:"port"`
LogLevel string `toml:"log_level"`
TLS bool `toml:"tls"`
CACerts string `toml:"ca_certs"`
ServerCert string `toml:"server_cert"`
ServerKey string `toml:"server_key"`
ThingsLocation string `toml:"things_location"`
@@ -56,6 +55,12 @@ type Gateway struct {
CfgID string `toml:"cfg_id" json:"cfg_id"`
}
type Certs struct {
HoursValid string `json:"days_valid" toml:"days_valid"`
KeyBits int `json:"key_bits" toml:"key_bits"`
KeyType string `json:"key_type"`
}
// Config struct of Provision
type Config struct {
File string `toml:"file"`
@@ -63,6 +68,7 @@ type Config struct {
Bootstrap Bootstrap `toml:"bootstrap" mapstructure:"bootstrap"`
Things []Thing `toml:"things" mapstructure:"things"`
Channels []Channel `toml:"channels" mapstructure:"channels"`
Certs Certs `toml:"certs" mapstructure:"certs"`
}
// Save - store config in a file
-342
View File
@@ -1,342 +0,0 @@
package mocks
import (
"sync"
"github.com/gofrs/uuid"
mfSDK "github.com/mainflux/mainflux/pkg/sdk/go"
)
const (
validEmail = "test@example.com"
validPass = "test"
invalid = "invalid"
validToken = "valid_token"
)
// SDK is fake sdk for mocking
type mockSDK struct {
things map[string]mfSDK.Thing
channels map[string]mfSDK.Channel
connections map[string][]string
configs map[string]mfSDK.BootstrapConfig
mu sync.Mutex
}
// NewSDK returns new mock SDK for testing purposes.
func NewSDK() mfSDK.SDK {
sdk := &mockSDK{}
sdk.channels = make(map[string]mfSDK.Channel)
sdk.connections = make(map[string][]string)
sdk.configs = make(map[string]mfSDK.BootstrapConfig)
th := mfSDK.Thing{ID: "predefined", Name: "ID"}
sdk.things = map[string]mfSDK.Thing{"predefined": th}
sdk.mu = sync.Mutex{}
return sdk
}
func (s *mockSDK) CreateUser(u mfSDK.User) error {
panic("CreatUser not implemented")
}
func (s *mockSDK) User(token string) (mfSDK.User, error) {
panic("User not implemented")
}
func (s *mockSDK) UpdateUser(u mfSDK.User, token string) error {
panic("UpdateUser not implemented")
}
func (s *mockSDK) UpdatePassword(oldPass, newPass, token string) error {
panic("UpdatePassword not implemented")
}
// CreateThings registers new things and returns their ids.
func (s *mockSDK) CreateThings(things []mfSDK.Thing, token string) ([]mfSDK.Thing, error) {
panic("CreateThings not implemented")
}
// Things returns page of things.
func (s *mockSDK) Things(token string, offset, limit uint64, name string) (mfSDK.ThingsPage, error) {
panic("Things not implemented")
}
// ThingsByChannel returns page of things that are connected to specified
// channel.
func (s *mockSDK) ThingsByChannel(token, chanID string, offset, limit uint64) (mfSDK.ThingsPage, error) {
panic("ThingsByChannel not implemented")
}
// UpdateThing updates existing thing.
func (s *mockSDK) UpdateThing(thing mfSDK.Thing, token string) error {
panic("UpdateThing not implemented")
}
// DisconnectThing disconnect thing from specified channel by id.
func (s *mockSDK) DisconnectThing(thingID, chanID, token string) error {
panic("UpdatePassword not implemented")
}
// CreateChannels registers new channels and returns their ids.
func (s *mockSDK) CreateChannels(channels []mfSDK.Channel, token string) ([]mfSDK.Channel, error) {
panic("CreateChannels not implemented")
}
// Channels returns page of channels.
func (s *mockSDK) Channels(token string, offset, limit uint64, name string) (mfSDK.ChannelsPage, error) {
panic("Channels not implemented")
}
// ChannelsByThing returns page of channels that are connected to specified
// thing.
func (s *mockSDK) ChannelsByThing(token, thingID string, offset, limit uint64) (mfSDK.ChannelsPage, error) {
panic("ChannelsByThing not implemented")
}
// UpdateChannel updates existing channel.
func (s *mockSDK) UpdateChannel(channel mfSDK.Channel, token string) error {
panic("UpdateChannel not implemented")
}
// SendMessage send message to specified channel.
func (s *mockSDK) SendMessage(chanID, msg, token string) error {
panic("SendMessage not implemented")
}
// ReadMessages read messages of specified channel.
func (s *mockSDK) ReadMessages(chanID, token string) (mfSDK.MessagesPage, error) {
panic("ReadMessages not implemented")
}
// SetContentType sets message content type.
func (s *mockSDK) SetContentType(ct mfSDK.ContentType) error {
panic("SetContentType not implemented")
}
// Version returns used mainflux version.
func (s *mockSDK) Version() (string, error) {
panic("Version not implemented")
}
// Update updates editable fields of the provided Config.
func (s *mockSDK) UpdateBootstrap(key string, cfg mfSDK.BootstrapConfig) error {
panic("UpdatePassword not implemented")
}
// View returns Thing Config with given ID belonging to the user identified by the given key.
func (s *mockSDK) Bootstrap(key, id string) (mfSDK.BootstrapConfig, error) {
panic("UpdatePassword not implemented")
}
// Whitelist updates Thing state Config with given ID belonging to the user identified by the given key.
func (s *mockSDK) Whitelist(key string, cfg mfSDK.BootstrapConfig) error {
if cfg.ThingID == invalid {
return mfSDK.ErrFailedWhitelist
}
return nil
}
func (s *mockSDK) CreateToken(u mfSDK.User) (string, error) {
if u.Email != validEmail || u.Password != validPass {
return "", mfSDK.ErrUnauthorized
}
return validToken, nil
}
func (s *mockSDK) CreateThing(t mfSDK.Thing, token string) (string, error) {
if token != validToken {
return "", mfSDK.ErrUnauthorized
}
id, err := uuid.NewV4()
if err != nil {
return "", err
}
key, err := uuid.NewV4()
if err != nil {
return "", err
}
s.mu.Lock()
defer s.mu.Unlock()
newThing := mfSDK.Thing{ID: id.String(), Name: t.Name, Key: key.String(), Metadata: t.Metadata}
s.things[newThing.ID] = newThing
return newThing.ID, nil
}
func (s *mockSDK) Thing(id, token string) (mfSDK.Thing, error) {
t := mfSDK.Thing{}
if token != validToken {
return t, mfSDK.ErrUnauthorized
}
s.mu.Lock()
defer s.mu.Unlock()
if t, ok := s.things[id]; ok {
return t, nil
}
return t, mfSDK.ErrFailedFetch
}
// Channel returns channel data by id.
func (s *mockSDK) Channel(id, token string) (mfSDK.Channel, error) {
c := mfSDK.Channel{}
if token != validToken {
return c, mfSDK.ErrUnauthorized
}
s.mu.Lock()
defer s.mu.Unlock()
if c, ok := s.channels[id]; ok {
return c, nil
}
return c, mfSDK.ErrFailedFetch
}
func (s *mockSDK) DeleteThing(id string, token string) error {
if id == invalid {
return mfSDK.ErrFailedRemoval
}
s.mu.Lock()
defer s.mu.Unlock()
delete(s.things, id)
return nil
}
func (s *mockSDK) CreateChannel(channel mfSDK.Channel, token string) (string, error) {
if token != validToken {
return "", mfSDK.ErrUnauthorized
}
id, err := uuid.NewV4()
if err != nil {
return "", err
}
s.mu.Lock()
defer s.mu.Unlock()
newChan := mfSDK.Channel{ID: id.String(), Name: channel.Name, Metadata: channel.Metadata}
s.channels[newChan.ID] = newChan
return newChan.ID, nil
}
func (s *mockSDK) DeleteChannel(id string, token string) error {
if id == invalid {
return mfSDK.ErrFailedRemoval
}
s.mu.Lock()
defer s.mu.Unlock()
delete(s.channels, id)
return nil
}
// ConnectThing connects thing to specified channel by id.
func (s *mockSDK) Connect(connIDs mfSDK.ConnectionIDs, token string) error {
if token != validToken {
return mfSDK.ErrUnauthorized
}
s.mu.Lock()
defer s.mu.Unlock()
for _, thingID := range connIDs.ThingIDs {
if _, ok := s.things[thingID]; !ok {
return mfSDK.ErrFailedFetch
}
}
for _, channelID := range connIDs.ChannelIDs {
if _, ok := s.channels[channelID]; !ok {
return mfSDK.ErrFailedFetch
}
}
for _, thingID := range connIDs.ThingIDs {
for _, chanID := range connIDs.ChannelIDs {
conns := s.connections[thingID]
conns = append(conns, chanID)
s.connections[thingID] = conns
}
}
return nil
}
func (s *mockSDK) AddBootstrap(token string, cfg mfSDK.BootstrapConfig) (string, error) {
if token != validToken {
return "", mfSDK.ErrUnauthorized
}
s.mu.Lock()
defer s.mu.Unlock()
for _, val := range s.configs {
if val.ExternalID == cfg.ExternalID {
return "", mfSDK.ErrFailedCreation
}
}
mfid, err := uuid.NewV4()
if err != nil {
return "", err
}
cfg.MFThing = mfid.String()
s.configs[string(mfid.String())] = cfg
return mfid.String(), nil
}
func (s *mockSDK) ViewBootstrap(token string, id string) (mfSDK.BootstrapConfig, error) {
if token != validToken {
return mfSDK.BootstrapConfig{}, mfSDK.ErrUnauthorized
}
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.configs[id]; !ok {
return mfSDK.BootstrapConfig{}, mfSDK.ErrFailedFetch
}
return s.configs[id], nil
}
func (s *mockSDK) RemoveBootstrap(token, id string) error {
if token != validToken {
return mfSDK.ErrUnauthorized
}
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.configs[id]; !ok {
return mfSDK.ErrFailedFetch
}
delete(s.configs, id)
return nil
}
func (s *mockSDK) Cert(thingID, thingKey string, token string) (mfSDK.Cert, error) {
if thingID == invalid || thingKey == invalid {
return mfSDK.Cert{}, mfSDK.ErrCerts
}
return mfSDK.Cert{}, nil
}
func (s *mockSDK) RemoveCert(key string, token string) error {
if key == invalid {
return mfSDK.ErrCertsRemove
}
return nil
}
+62 -20
View File
@@ -44,7 +44,14 @@ type Service interface {
// - create multiple Channels
// - create Bootstrap configuration
// - whitelist Thing in Bootstrap configuration == connect Thing to Channels
Provision(name, token, externalID, externalKey string) (Result, error)
Provision(token, name, externalID, externalKey string) (Result, error)
// Certs creates certificate for things that communicate over mTLS
// A duration string is a possibly signed sequence of decimal numbers,
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
// keyBits for certificate key
Cert(token, thingID, duration string, keyBits int) (string, string, error)
}
type provisionService struct {
@@ -75,26 +82,14 @@ func New(cfg Config, sdk SDK.SDK, logger logger.Logger) Service {
// Provision is provision method for creating setup according to
// provision layout specified in config.toml
func (ps *provisionService) Provision(name, token, externalID, externalKey string) (res Result, err error) {
func (ps *provisionService) Provision(token, name, externalID, externalKey string) (res Result, err error) {
var channels []SDK.Channel
var things []SDK.Thing
defer ps.recover(&err, &things, &channels, &token)
if token == "" {
token = ps.conf.Server.MfAPIKey
if token == "" {
if ps.conf.Server.MfUser == "" || ps.conf.Server.MfPass == "" {
return res, ErrMissingCredentials
}
u := SDK.User{
Email: ps.conf.Server.MfUser,
Password: ps.conf.Server.MfPass,
}
token, err = ps.sdk.CreateToken(u)
if err != nil {
return res, errors.Wrap(ErrFailedToCreateToken, err)
}
}
token, err = ps.createTokenIfEmpty(token)
if err != nil {
return res, err
}
if len(ps.conf.Things) == 0 {
@@ -190,11 +185,14 @@ func (ps *provisionService) Provision(name, token, externalID, externalKey strin
}
if ps.conf.Bootstrap.X509Provision {
cert, err = ps.sdk.Cert(thing.ID, thing.Key, token)
var cert SDK.Cert
cert, err = ps.sdk.IssueCert(thing.ID, ps.conf.Certs.KeyBits, ps.conf.Certs.KeyType, ps.conf.Certs.HoursValid, token)
if err != nil {
e := errors.Wrap(err, fmt.Errorf("thing id: %s", thing.ID))
return res, errors.Wrap(ErrFailedCertCreation, e)
}
res.ClientCert[thing.ID] = cert.ClientCert
res.ClientKey[thing.ID] = cert.ClientKey
res.CACert = cert.CACert
@@ -214,10 +212,54 @@ func (ps *provisionService) Provision(name, token, externalID, externalKey strin
}
ps.updateGateway(token, bs, channels)
if err = ps.updateGateway(token, bs, channels); err != nil {
return res, err
}
return res, nil
}
func (ps *provisionService) Cert(token, thingID, daysValid string, keyBits int) (string, string, error) {
token, err := ps.createTokenIfEmpty(token)
if err != nil {
return "", "", err
}
th, err := ps.sdk.Thing(thingID, token)
if err != nil {
return "", "", errors.Wrap(SDK.ErrUnauthorized, err)
}
cert, err := ps.sdk.IssueCert(th.ID, ps.conf.Certs.KeyBits, ps.conf.Certs.KeyType, ps.conf.Certs.HoursValid, token)
return cert.ClientCert, cert.ClientKey, err
}
func (ps *provisionService) createTokenIfEmpty(token string) (string, error) {
if token != "" {
return token, nil
}
// If no token in request is provided
// use API key provided in config file or env
if ps.conf.Server.MfAPIKey != "" {
return ps.conf.Server.MfAPIKey, nil
}
// If no API key use username and password provided to create access token.
if ps.conf.Server.MfUser == "" || ps.conf.Server.MfPass == "" {
return token, ErrMissingCredentials
}
u := SDK.User{
Email: ps.conf.Server.MfUser,
Password: ps.conf.Server.MfPass,
}
token, err := ps.sdk.CreateToken(u)
if err != nil {
return token, errors.Wrap(ErrFailedToCreateToken, err)
}
return token, nil
}
func (ps *provisionService) updateGateway(token string, bs SDK.BootstrapConfig, channels []SDK.Channel) error {
var gw Gateway
for _, ch := range channels {
@@ -289,7 +331,7 @@ func (ps *provisionService) recover(e *error, ths *[]SDK.Thing, chs *[]SDK.Chann
return
}
if errors.Contains(err, ErrFailedBootstrap) {
if errors.Contains(err, ErrFailedBootstrap) || errors.Contains(err, ErrFailedCertCreation) {
clean(ps, things, channels, token)
if ps.conf.Bootstrap.X509Provision {
for _, th := range things {
-81
View File
@@ -1,81 +0,0 @@
package provision_test
import (
"fmt"
"os"
"testing"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/provision"
"github.com/mainflux/mainflux/provision/mocks"
logger "github.com/mainflux/mainflux/logger"
"github.com/stretchr/testify/assert"
)
var (
cfg = provision.Config{
Bootstrap: provision.Bootstrap{
AutoWhiteList: true,
Provision: true,
Content: "",
X509Provision: true,
},
Server: provision.ServiceConf{
MfPass: "test",
MfUser: "test@example.com",
},
Channels: []provision.Channel{
provision.Channel{
Name: "control-channel",
Metadata: map[string]interface{}{"type": "control"},
},
provision.Channel{
Name: "data-channel",
Metadata: map[string]interface{}{"type": "data"},
},
},
Things: []provision.Thing{
provision.Thing{
Name: "thing",
Metadata: map[string]interface{}{"external_id": "xxxxxx"},
},
},
}
log, _ = logger.New(os.Stdout, "info")
)
func TestProvision(t *testing.T) {
// Create multiple services with different configurations.
sdk := mocks.NewSDK()
svc := provision.New(cfg, sdk, log)
cases := []struct {
desc string
externalID string
externalKey string
svc provision.Service
err error
}{
{
desc: "Provision successfully",
externalID: "id",
externalKey: "key",
svc: svc,
err: nil,
},
{
desc: "Provision already existing config",
externalID: "id",
externalKey: "key",
svc: svc,
err: provision.ErrFailedBootstrap,
},
}
for _, tc := range cases {
_, err := tc.svc.Provision("", "", tc.externalID, tc.externalKey)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ func TestMain(m *testing.M) {
code := m.Run()
// defers will not be run when using os.Exit
// Defers will not be run when using os.Exit
db.Close()
if err = pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
+1 -1
View File
@@ -74,7 +74,7 @@ func TestMain(m *testing.M) {
code := m.Run()
// defers will not be run when using os.Exit
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
+1 -1
View File
@@ -68,7 +68,7 @@ func TestMain(m *testing.M) {
code := m.Run()
// defers will not be run when using os.Exit
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
+354
View File
@@ -0,0 +1,354 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.
+89
View File
@@ -0,0 +1,89 @@
# errwrap
`errwrap` is a package for Go that formalizes the pattern of wrapping errors
and checking if an error contains another error.
There is a common pattern in Go of taking a returned `error` value and
then wrapping it (such as with `fmt.Errorf`) before returning it. The problem
with this pattern is that you completely lose the original `error` structure.
Arguably the _correct_ approach is that you should make a custom structure
implementing the `error` interface, and have the original error as a field
on that structure, such [as this example](http://golang.org/pkg/os/#PathError).
This is a good approach, but you have to know the entire chain of possible
rewrapping that happens, when you might just care about one.
`errwrap` formalizes this pattern (it doesn't matter what approach you use
above) by giving a single interface for wrapping errors, checking if a specific
error is wrapped, and extracting that error.
## Installation and Docs
Install using `go get github.com/hashicorp/errwrap`.
Full documentation is available at
http://godoc.org/github.com/hashicorp/errwrap
## Usage
#### Basic Usage
Below is a very basic example of its usage:
```go
// A function that always returns an error, but wraps it, like a real
// function might.
func tryOpen() error {
_, err := os.Open("/i/dont/exist")
if err != nil {
return errwrap.Wrapf("Doesn't exist: {{err}}", err)
}
return nil
}
func main() {
err := tryOpen()
// 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, "does not exist") {
// Do something
}
if errwrap.ContainsType(err, new(os.PathError)) {
// Do something
}
// Or we can use the associated `Get` functions to just extract
// a specific error. This would return nil if that specific error doesn't
// exist.
perr := errwrap.GetType(err, new(os.PathError))
}
```
#### Custom Types
If you're already making custom types that properly wrap errors, then
you can get all the functionality of `errwraps.Contains` and such by
implementing the `Wrapper` interface with just one function. Example:
```go
type AppError {
Code ErrorCode
Err error
}
func (e *AppError) WrappedErrors() []error {
return []error{e.Err}
}
```
Now this works:
```go
err := &AppError{Err: fmt.Errorf("an error")}
if errwrap.ContainsType(err, fmt.Errorf("")) {
// This will work!
}
```
+169
View File
@@ -0,0 +1,169 @@
// Package errwrap implements methods to formalize error wrapping in Go.
//
// All of the top-level functions that take an `error` are built to be able
// to take any error, not just wrapped errors. This allows you to use errwrap
// without having to type-check and type-cast everywhere.
package errwrap
import (
"errors"
"reflect"
"strings"
)
// WalkFunc is the callback called for Walk.
type WalkFunc func(error)
// Wrapper is an interface that can be implemented by custom types to
// have all the Contains, Get, etc. functions in errwrap work.
//
// When Walk reaches a Wrapper, it will call the callback for every
// wrapped error in addition to the wrapper itself. Since all the top-level
// functions in errwrap use Walk, this means that all those functions work
// with your custom type.
type Wrapper interface {
WrappedErrors() []error
}
// Wrap defines that outer wraps inner, returning an error type that
// can be cleanly used with the other methods in this package, such as
// Contains, GetAll, etc.
//
// This function won't modify the error message at all (the outer message
// will be used).
func Wrap(outer, inner error) error {
return &wrappedError{
Outer: outer,
Inner: inner,
}
}
// Wrapf wraps an error with a formatting message. This is similar to using
// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap
// errors, you should replace it with this.
//
// format is the format of the error message. The string '{{err}}' will
// be replaced with the original error message.
func Wrapf(format string, err error) error {
outerMsg := "<nil>"
if err != nil {
outerMsg = err.Error()
}
outer := errors.New(strings.Replace(
format, "{{err}}", outerMsg, -1))
return Wrap(outer, err)
}
// Contains checks if the given error contains an error with the
// message msg. If err is not a wrapped error, this will always return
// false unless the error itself happens to match this msg.
func Contains(err error, msg string) bool {
return len(GetAll(err, msg)) > 0
}
// ContainsType checks if the given error contains an error with
// the same concrete type as v. If err is not a wrapped error, this will
// check the err itself.
func ContainsType(err error, v interface{}) bool {
return len(GetAllType(err, v)) > 0
}
// Get is the same as GetAll but returns the deepest matching error.
func Get(err error, msg string) error {
es := GetAll(err, msg)
if len(es) > 0 {
return es[len(es)-1]
}
return nil
}
// GetType is the same as GetAllType but returns the deepest matching error.
func GetType(err error, v interface{}) error {
es := GetAllType(err, v)
if len(es) > 0 {
return es[len(es)-1]
}
return nil
}
// GetAll gets all the errors that might be wrapped in err with the
// given message. The order of the errors is such that the outermost
// matching error (the most recent wrap) is index zero, and so on.
func GetAll(err error, msg string) []error {
var result []error
Walk(err, func(err error) {
if err.Error() == msg {
result = append(result, err)
}
})
return result
}
// GetAllType gets all the errors that are the same type as v.
//
// The order of the return value is the same as described in GetAll.
func GetAllType(err error, v interface{}) []error {
var result []error
var search string
if v != nil {
search = reflect.TypeOf(v).String()
}
Walk(err, func(err error) {
var needle string
if err != nil {
needle = reflect.TypeOf(err).String()
}
if needle == search {
result = append(result, err)
}
})
return result
}
// Walk walks all the wrapped errors in err and calls the callback. If
// err isn't a wrapped error, this will be called once for err. If err
// is a wrapped error, the callback will be called for both the wrapper
// that implements error as well as the wrapped error itself.
func Walk(err error, cb WalkFunc) {
if err == nil {
return
}
switch e := err.(type) {
case *wrappedError:
cb(e.Outer)
Walk(e.Inner, cb)
case Wrapper:
cb(err)
for _, err := range e.WrappedErrors() {
Walk(err, cb)
}
default:
cb(err)
}
}
// wrappedError is an implementation of error that has both the
// outer and inner errors.
type wrappedError struct {
Outer error
Inner error
}
func (w *wrappedError) Error() string {
return w.Outer.Error()
}
func (w *wrappedError) WrappedErrors() []error {
return []error{w.Outer, w.Inner}
}
+1
View File
@@ -0,0 +1 @@
module github.com/hashicorp/errwrap
+363
View File
@@ -0,0 +1,363 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.
+30
View File
@@ -0,0 +1,30 @@
# cleanhttp
Functions for accessing "clean" Go http.Client values
-------------
The Go standard library contains a default `http.Client` called
`http.DefaultClient`. It is a common idiom in Go code to start with
`http.DefaultClient` and tweak it as necessary, and in fact, this is
encouraged; from the `http` package documentation:
> The Client's Transport typically has internal state (cached TCP connections),
so Clients should be reused instead of created as needed. Clients are safe for
concurrent use by multiple goroutines.
Unfortunately, this is a shared value, and it is not uncommon for libraries to
assume that they are free to modify it at will. With enough dependencies, it
can be very easy to encounter strange problems and race conditions due to
manipulation of this shared value across libraries and goroutines (clients are
safe for concurrent use, but writing values to the client struct itself is not
protected).
Making things worse is the fact that a bare `http.Client` will use a default
`http.Transport` called `http.DefaultTransport`, which is another global value
that behaves the same way. So it is not simply enough to replace
`http.DefaultClient` with `&http.Client{}`.
This repository provides some simple functions to get a "clean" `http.Client`
-- one that uses the same default values as the Go standard library, but
returns a client that does not share any state with other clients.
+57
View File
@@ -0,0 +1,57 @@
package cleanhttp
import (
"net"
"net/http"
"runtime"
"time"
)
// DefaultTransport returns a new http.Transport with similar default values to
// http.DefaultTransport, but with idle connections and keepalives disabled.
func DefaultTransport() *http.Transport {
transport := DefaultPooledTransport()
transport.DisableKeepAlives = true
transport.MaxIdleConnsPerHost = -1
return transport
}
// DefaultPooledTransport returns a new http.Transport with similar default
// values to http.DefaultTransport. Do not use this for transient transports as
// it can leak file descriptors over time. Only use this for transports that
// will be re-used for the same host(s).
func DefaultPooledTransport() *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
}
return transport
}
// DefaultClient returns a new http.Client with similar default values to
// http.Client, but with a non-shared Transport, idle connections disabled, and
// keepalives disabled.
func DefaultClient() *http.Client {
return &http.Client{
Transport: DefaultTransport(),
}
}
// DefaultPooledClient returns a new http.Client with similar default values to
// http.Client, but with a shared Transport. Do not use this function for
// transient clients as it can leak file descriptors over time. Only use this
// for clients that will be re-used for the same host(s).
func DefaultPooledClient() *http.Client {
return &http.Client{
Transport: DefaultPooledTransport(),
}
}
+20
View File
@@ -0,0 +1,20 @@
// Package cleanhttp offers convenience utilities for acquiring "clean"
// http.Transport and http.Client structs.
//
// Values set on http.DefaultClient and http.DefaultTransport affect all
// callers. This can have detrimental effects, esepcially in TLS contexts,
// where client or root certificates set to talk to multiple endpoints can end
// up displacing each other, leading to hard-to-debug issues. This package
// provides non-shared http.Client and http.Transport structs to ensure that
// the configuration will not be overwritten by other parts of the application
// or dependencies.
//
// The DefaultClient and DefaultTransport functions disable idle connections
// and keepalives. Without ensuring that idle connections are closed before
// garbage collection, short-term clients/transports can leak file descriptors,
// eventually leading to "too many open files" errors. If you will be
// connecting to the same hosts repeatedly from the same client, you can use
// DefaultPooledClient to receive a client that has connection pooling
// semantics similar to http.DefaultClient.
//
package cleanhttp
+1
View File
@@ -0,0 +1 @@
module github.com/hashicorp/go-cleanhttp
+48
View File
@@ -0,0 +1,48 @@
package cleanhttp
import (
"net/http"
"strings"
"unicode"
)
// HandlerInput provides input options to cleanhttp's handlers
type HandlerInput struct {
ErrStatus int
}
// PrintablePathCheckHandler is a middleware that ensures the request path
// contains only printable runes.
func PrintablePathCheckHandler(next http.Handler, input *HandlerInput) http.Handler {
// Nil-check on input to make it optional
if input == nil {
input = &HandlerInput{
ErrStatus: http.StatusBadRequest,
}
}
// Default to http.StatusBadRequest on error
if input.ErrStatus == 0 {
input.ErrStatus = http.StatusBadRequest
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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 next != nil {
next.ServeHTTP(w, r)
}
}
return
})
}
+12
View File
@@ -0,0 +1,12 @@
sudo: false
language: go
go:
- 1.x
branches:
only:
- master
script: make test testrace
+353
View File
@@ -0,0 +1,353 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.
+31
View File
@@ -0,0 +1,31 @@
TEST?=./...
default: test
# test runs the test suite and vets the code.
test: generate
@echo "==> Running tests..."
@go list $(TEST) \
| grep -v "/vendor/" \
| xargs -n1 go test -timeout=60s -parallel=10 ${TESTARGS}
# testrace runs the race checker
testrace: generate
@echo "==> Running tests (race)..."
@go list $(TEST) \
| grep -v "/vendor/" \
| xargs -n1 go test -timeout=60s -race ${TESTARGS}
# updatedeps installs all the dependencies needed to run and build.
updatedeps:
@sh -c "'${CURDIR}/scripts/deps.sh' '${NAME}'"
# generate runs `go generate` to build the dynamically generated source files.
generate:
@echo "==> Generating..."
@find . -type f -name '.DS_Store' -delete
@go list ./... \
| grep -v "/vendor/" \
| xargs -n1 go generate
.PHONY: default test testrace updatedeps generate
+97
View File
@@ -0,0 +1,97 @@
# go-multierror
[![Build Status](http://img.shields.io/travis/hashicorp/go-multierror.svg?style=flat-square)][travis]
[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
[travis]: https://travis-ci.org/hashicorp/go-multierror
[godocs]: https://godoc.org/github.com/hashicorp/go-multierror
`go-multierror` is a package for Go that provides a mechanism for
representing a list of `error` values as a single `error`.
This allows a function in Go to return an `error` that might actually
be a list of errors. If the caller knows this, they can unwrap the
list and access the errors. If the caller doesn't know, the error
formats to a nice human-readable format.
`go-multierror` implements the
[errwrap](https://github.com/hashicorp/errwrap) interface so that it can
be used with that library, as well.
## Installation and Docs
Install using `go get github.com/hashicorp/go-multierror`.
Full documentation is available at
http://godoc.org/github.com/hashicorp/go-multierror
## Usage
go-multierror is easy to use and purposely built to be unobtrusive in
existing Go applications/libraries that may not be aware of it.
**Building a list of errors**
The `Append` function is used to create a list of errors. This function
behaves a lot like the Go built-in `append` function: it doesn't matter
if the first argument is nil, a `multierror.Error`, or any other `error`,
the function behaves as you would expect.
```go
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
return result
```
**Customizing the formatting of the errors**
By specifying a custom `ErrorFormat`, you can customize the format
of the `Error() string` function:
```go
var result *multierror.Error
// ... accumulate errors here, maybe using Append
if result != nil {
result.ErrorFormat = func([]error) string {
return "errors!"
}
}
```
**Accessing the list of errors**
`multierror.Error` implements `error` so if the caller doesn't know about
multierror, it will work just fine. But if you're aware a multierror might
be returned, you can use type switches to access the list of errors:
```go
if err := something(); err != nil {
if merr, ok := err.(*multierror.Error); ok {
// Use merr.Errors
}
}
```
**Returning a multierror only if there are errors**
If you build a `multierror.Error`, you can use the `ErrorOrNil` function
to return an `error` implementation only if there are errors to return:
```go
var result *multierror.Error
// ... accumulate errors here
// Return the `error` only if errors were added to the multierror, otherwise
// return nil since there are no errors.
return result.ErrorOrNil()
```
+41
View File
@@ -0,0 +1,41 @@
package multierror
// Append is a helper function that will append more errors
// onto an Error in order to create a larger multi-error.
//
// If err is not a multierror.Error, then it will be turned into
// one. If any of the errs are multierr.Error, they will be flattened
// one level into err.
func Append(err error, errs ...error) *Error {
switch err := err.(type) {
case *Error:
// Typed nils can reach here, so initialize if we are nil
if err == nil {
err = new(Error)
}
// Go through each error and flatten
for _, e := range errs {
switch e := e.(type) {
case *Error:
if e != nil {
err.Errors = append(err.Errors, e.Errors...)
}
default:
if e != nil {
err.Errors = append(err.Errors, e)
}
}
}
return err
default:
newErrs := make([]error, 0, len(errs)+1)
if err != nil {
newErrs = append(newErrs, err)
}
newErrs = append(newErrs, errs...)
return Append(&Error{}, newErrs...)
}
}
+26
View File
@@ -0,0 +1,26 @@
package multierror
// Flatten flattens the given error, merging any *Errors together into
// a single *Error.
func Flatten(err error) error {
// If it isn't an *Error, just return the error as-is
if _, ok := err.(*Error); !ok {
return err
}
// Otherwise, make the result and flatten away!
flatErr := new(Error)
flatten(err, flatErr)
return flatErr
}
func flatten(err error, flatErr *Error) {
switch err := err.(type) {
case *Error:
for _, e := range err.Errors {
flatten(e, flatErr)
}
default:
flatErr.Errors = append(flatErr.Errors, err)
}
}
+27
View File
@@ -0,0 +1,27 @@
package multierror
import (
"fmt"
"strings"
)
// ErrorFormatFunc is a function callback that is called by Error to
// turn the list of errors into a string.
type ErrorFormatFunc func([]error) string
// ListFormatFunc is a basic formatter that outputs the number of errors
// that occurred along with a bullet point list of the errors.
func ListFormatFunc(es []error) string {
if len(es) == 1 {
return fmt.Sprintf("1 error occurred:\n\t* %s\n\n", es[0])
}
points := make([]string, len(es))
for i, err := range es {
points[i] = fmt.Sprintf("* %s", err)
}
return fmt.Sprintf(
"%d errors occurred:\n\t%s\n\n",
len(es), strings.Join(points, "\n\t"))
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/hashicorp/go-multierror
require github.com/hashicorp/errwrap v1.0.0
+4
View File
@@ -0,0 +1,4 @@
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce h1:prjrVgOk2Yg6w+PflHoszQNLTUh4kaByUcEWM/9uin4=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
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=
+51
View File
@@ -0,0 +1,51 @@
package multierror
import (
"fmt"
)
// Error is an error type to track multiple errors. This is used to
// accumulate errors in cases and return them as a single "error".
type Error struct {
Errors []error
ErrorFormat ErrorFormatFunc
}
func (e *Error) Error() string {
fn := e.ErrorFormat
if fn == nil {
fn = ListFormatFunc
}
return fn(e.Errors)
}
// ErrorOrNil returns an error interface if this Error represents
// a list of errors, or returns nil if the list of errors is empty. This
// function is useful at the end of accumulation to make sure that the value
// returned represents the existence of errors.
func (e *Error) ErrorOrNil() error {
if e == nil {
return nil
}
if len(e.Errors) == 0 {
return nil
}
return e
}
func (e *Error) GoString() string {
return fmt.Sprintf("*%#v", *e)
}
// WrappedErrors returns the list of errors that this Error is wrapping.
// It is an implementation of the errwrap.Wrapper interface so that
// multierror.Error can be used with that library.
//
// This method is not safe to be called concurrently and is no different
// than accessing the Errors field directly. It is implemented only to
// satisfy the errwrap.Wrapper interface.
func (e *Error) WrappedErrors() []error {
return e.Errors
}
+37
View File
@@ -0,0 +1,37 @@
package multierror
import (
"fmt"
"github.com/hashicorp/errwrap"
)
// Prefix is a helper function that will prefix some text
// to the given error. If the error is a multierror.Error, then
// it will be prefixed to each wrapped error.
//
// This is useful to use when appending multiple multierrors
// together in order to give better scoping.
func Prefix(err error, prefix string) error {
if err == nil {
return nil
}
format := fmt.Sprintf("%s {{err}}", prefix)
switch err := err.(type) {
case *Error:
// Typed nils can reach here, so initialize if we are nil
if err == nil {
err = new(Error)
}
// Wrap each of the errors
for i, e := range err.Errors {
err.Errors[i] = errwrap.Wrapf(format, e)
}
return err
default:
return errwrap.Wrapf(format, err)
}
}
+16
View File
@@ -0,0 +1,16 @@
package multierror
// Len implements sort.Interface function for length
func (err Error) Len() int {
return len(err.Errors)
}
// Swap implements sort.Interface function for swapping elements
func (err Error) Swap(i, j int) {
err.Errors[i], err.Errors[j] = err.Errors[j], err.Errors[i]
}
// Less implements sort.Interface function for determining order
func (err Error) Less(i, j int) bool {
return err.Errors[i].Error() < err.Errors[j].Error()
}
+4
View File
@@ -0,0 +1,4 @@
.idea/
*.iml
*.test
.vscode/
+12
View File
@@ -0,0 +1,12 @@
sudo: false
language: go
go:
- 1.12.4
branches:
only:
- master
script: make updatedeps test
+363
View File
@@ -0,0 +1,363 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.
+11
View File
@@ -0,0 +1,11 @@
default: test
test:
go vet ./...
go test -race ./...
updatedeps:
go get -f -t -u ./...
go get -f -u ./...
.PHONY: default test updatedeps
+46
View File
@@ -0,0 +1,46 @@
go-retryablehttp
================
[![Build Status](http://img.shields.io/travis/hashicorp/go-retryablehttp.svg?style=flat-square)][travis]
[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
[travis]: http://travis-ci.org/hashicorp/go-retryablehttp
[godocs]: http://godoc.org/github.com/hashicorp/go-retryablehttp
The `retryablehttp` package provides a familiar HTTP client interface with
automatic retries and exponential backoff. It is a thin wrapper over the
standard `net/http` client library and exposes nearly the same public API. This
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 (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) 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
===========
Using this library should look almost identical to what you would do with
`net/http`. The most simple example of a GET request is shown below:
```go
resp, err := retryablehttp.Get("/foo")
if err != nil {
panic(err)
}
```
The returned response object is an `*http.Response`, the same thing you would
usually get from `net/http`. Had the request failed one or more times, the above
call would block and retry with exponential backoff.
For more usage and examples see the
[godoc](http://godoc.org/github.com/hashicorp/go-retryablehttp).
+549
View File
@@ -0,0 +1,549 @@
// The retryablehttp package provides a familiar HTTP client interface with
// automatic retries and exponential backoff. It is a thin wrapper over the
// standard net/http client library and exposes nearly the same public API.
// This 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 is received, then a retry is invoked. Otherwise, the response is
// returned and left to the caller to interpret.
//
// 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"
"log"
"math"
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"time"
cleanhttp "github.com/hashicorp/go-cleanhttp"
)
var (
// Default retry configuration
defaultRetryWaitMin = 1 * time.Second
defaultRetryWaitMax = 30 * time.Second
defaultRetryMax = 4
// defaultClient is used for performing requests without explicitly making
// a new client. It is purposely private to avoid modifications.
defaultClient = NewClient()
// We need to consume response bodies to maintain http connections, but
// limit the size we consume to respReadLimit.
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 {
Len() int
}
// Request wraps the metadata needed to create HTTP requests.
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 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
}
// 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
}
// 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
}
httpReq, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
httpReq.ContentLength = contentLength
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(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(Logger, *http.Response)
// CheckRetry specifies a policy for handling retries. It is called
// following each request with the response and error values returned by
// the http.Client. If CheckRetry returns false, the Client stops retrying
// and returns the response to the caller. If CheckRetry returns an error,
// that error value is returned in lieu of the error from the request. The
// 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(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
// that should pass before trying again.
type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration
// ErrorHandler is called if retries are expired, containing the last status
// from the http library. If not specified, default behavior for the library is
// to close the body and return an error indicating how many tries were
// attempted. If overriding this, be sure to close the body if needed.
type ErrorHandler func(resp *http.Response, err error, numTries int) (*http.Response, error)
// Client is used to make HTTP requests. It adds additional functionality
// like automatic retries to tolerate minor outages.
type Client struct {
HTTPClient *http.Client // Internal HTTP client.
Logger Logger // Customer logger instance.
RetryWaitMin time.Duration // Minimum time to wait
RetryWaitMax time.Duration // Maximum time to wait
RetryMax int // Maximum number of retries
// RequestLogHook allows a user-supplied function to be called
// before each retry.
RequestLogHook RequestLogHook
// ResponseLogHook allows a user-supplied function to be called
// with the response from each HTTP request executed.
ResponseLogHook ResponseLogHook
// CheckRetry specifies the policy for handling retries, and is called
// after each request. The default policy is DefaultRetryPolicy.
CheckRetry CheckRetry
// Backoff specifies the policy for how long to wait between retries
Backoff Backoff
// ErrorHandler specifies the custom error handler to use, if any
ErrorHandler ErrorHandler
}
// NewClient creates a new Client with default settings.
func NewClient() *Client {
return &Client{
HTTPClient: cleanhttp.DefaultClient(),
Logger: log.New(os.Stderr, "", log.LstdFlags),
RetryWaitMin: defaultRetryWaitMin,
RetryWaitMax: defaultRetryWaitMax,
RetryMax: defaultRetryMax,
CheckRetry: DefaultRetryPolicy,
Backoff: DefaultBackoff,
}
}
// DefaultRetryPolicy provides a default callback for Client.CheckRetry, which
// will retry on connection errors and server errors.
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
}
// Check the response code. We retry on 500-range responses to allow
// 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 && resp.StatusCode != 501) {
return true, nil
}
return false, nil
}
// DefaultBackoff provides a default callback for Client.Backoff which
// will perform exponential backoff based on the attempt number and limited
// by the provided minimum and maximum durations.
func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
mult := math.Pow(2, float64(attemptNum)) * float64(min)
sleep := time.Duration(mult)
if float64(sleep) != mult || sleep > max {
sleep = max
}
return sleep
}
// LinearJitterBackoff provides a callback for Client.Backoff which will
// perform linear backoff based on the attempt number and with jitter to
// prevent a thundering herd.
//
// min and max here are *not* absolute values. The number to be multipled by
// the attempt number will be chosen at random from between them, thus they are
// bounding the jitter.
//
// For instance:
// * To get strictly linear backoff of one second increasing each retry, set
// both to one second (1s, 2s, 3s, 4s, ...)
// * To get a small amount of jitter centered around one second increasing each
// retry, set to around one second, such as a min of 800ms and max of 1200ms
// (892ms, 2102ms, 2945ms, 4312ms, ...)
// * To get extreme jitter, set to a very wide spread, such as a min of 100ms
// and a max of 20s (15382ms, 292ms, 51321ms, 35234ms, ...)
func LinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
// attemptNum always starts at zero but we want to start at 1 for multiplication
attemptNum++
if max <= min {
// Unclear what to do here, or they are the same, so return min *
// attemptNum
return min * time.Duration(attemptNum)
}
// Seed rand; doing this every time is fine
rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
// Pick a random number that lies somewhere between the min and max and
// multiply by the attemptNum. attemptNum starts at zero so we always
// increment here. We first get a random percentage, then apply that to the
// difference between min and max, and add to min.
jitter := rand.Float64() * float64(max-min)
jitterMin := int64(jitter) + int64(min)
return time.Duration(jitterMin * int64(attemptNum))
}
// PassthroughErrorHandler is an ErrorHandler that directly passes through the
// values from the net/http library for the final request. The body is not
// closed.
func PassthroughErrorHandler(resp *http.Response, err error, _ int) (*http.Response, error) {
return resp, err
}
// Do wraps calling an HTTP method with retries.
func (c *Client) Do(req *Request) (*http.Response, error) {
if c.Logger != nil {
c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL)
}
var resp *http.Response
var err error
for i := 0; ; i++ {
var code int // HTTP response code
// Always rewind the request body when non-nil.
if req.body != nil {
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)
}
}
if c.RequestLogHook != nil {
c.RequestLogHook(c.Logger, req.Request, i)
}
// Attempt the request
resp, err = c.HTTPClient.Do(req.Request)
if resp != nil {
code = resp.StatusCode
}
// Check if we should continue with retries.
checkOK, checkErr := c.CheckRetry(req.Context(), resp, err)
if err != nil {
if c.Logger != nil {
c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
}
} else {
// Call this here to maintain the behavior of logging all requests,
// even if CheckRetry signals to stop.
if c.ResponseLogHook != nil {
// Call the response logger function if provided.
c.ResponseLogHook(c.Logger, resp)
}
}
// Now decide if we should continue.
if !checkOK {
if checkErr != nil {
err = checkErr
}
return resp, err
}
// 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 {
break
}
// We're going to retry, consume any response to reuse the connection.
if err == nil && resp != nil {
c.drainBody(resp.Body)
}
wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp)
desc := fmt.Sprintf("%s %s", req.Method, req.URL)
if code > 0 {
desc = fmt.Sprintf("%s (status: %d)", desc, code)
}
if c.Logger != nil {
c.Logger.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain)
}
select {
case <-req.Context().Done():
return nil, req.Context().Err()
case <-time.After(wait):
}
}
if c.ErrorHandler != nil {
return c.ErrorHandler(resp, err, c.RetryMax+1)
}
// By default, we close the response body and return an error without
// returning the response
if resp != nil {
resp.Body.Close()
}
return nil, fmt.Errorf("%s %s giving up after %d attempts",
req.Method, req.URL, c.RetryMax+1)
}
// Try to read the response body so we can reuse this connection.
func (c *Client) drainBody(body io.ReadCloser) {
defer body.Close()
_, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit))
if err != nil {
if c.Logger != nil {
c.Logger.Printf("[ERR] error reading response body: %v", err)
}
}
}
// Get is a shortcut for doing a GET request without making a new client.
func Get(url string) (*http.Response, error) {
return defaultClient.Get(url)
}
// Get is a convenience helper for doing simple GET requests.
func (c *Client) Get(url string) (*http.Response, error) {
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Head is a shortcut for doing a HEAD request without making a new client.
func Head(url string) (*http.Response, error) {
return defaultClient.Head(url)
}
// Head is a convenience method for doing simple HEAD requests.
func (c *Client) Head(url string) (*http.Response, error) {
req, err := NewRequest("HEAD", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Post is a shortcut for doing a POST request without making a new client.
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 interface{}) (*http.Response, error) {
req, err := NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return c.Do(req)
}
// PostForm is a shortcut to perform a POST with form data without creating
// a new client.
func PostForm(url string, data url.Values) (*http.Response, error) {
return defaultClient.PostForm(url, data)
}
// PostForm is a convenience method for doing simple POST operations using
// pre-filled url.Values form data.
func (c *Client) PostForm(url string, data url.Values) (*http.Response, error) {
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/hashicorp/go-retryablehttp
require github.com/hashicorp/go-cleanhttp v0.5.0
+2
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=
+12
View File
@@ -0,0 +1,12 @@
sudo: false
language: go
go:
- 1.6
branches:
only:
- master
script: make test
+363
View File
@@ -0,0 +1,363 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.
+8
View File
@@ -0,0 +1,8 @@
TEST?=./...
test:
go test $(TEST) $(TESTARGS) -timeout=3s -parallel=4
go vet $(TEST)
go test $(TEST) -race
.PHONY: test
+43
View File
@@ -0,0 +1,43 @@
# rootcerts
Functions for loading root certificates for TLS connections.
-----
Go's standard library `crypto/tls` provides a common mechanism for configuring
TLS connections in `tls.Config`. The `RootCAs` field on this struct is a pool
of certificates for the client to use as a trust store when verifying server
certificates.
This library contains utility functions for loading certificates destined for
that field, as well as one other important thing:
When the `RootCAs` field is `nil`, the standard library attempts to load the
host's root CA set. This behavior is OS-specific, and the Darwin
implementation contains [a bug that prevents trusted certificates from the
System and Login keychains from being loaded][1]. This library contains
Darwin-specific behavior that works around that bug.
[1]: https://github.com/golang/go/issues/14514
## Example Usage
Here's a snippet demonstrating how this library is meant to be used:
```go
func httpClient() (*http.Client, error)
tlsConfig := &tls.Config{}
err := rootcerts.ConfigureTLS(tlsConfig, &rootcerts.Config{
CAFile: os.Getenv("MYAPP_CAFILE"),
CAPath: os.Getenv("MYAPP_CAPATH"),
})
if err != nil {
return nil, err
}
c := cleanhttp.DefaultClient()
t := cleanhttp.DefaultTransport()
t.TLSClientConfig = tlsConfig
c.Transport = t
return c, nil
}
```
+9
View File
@@ -0,0 +1,9 @@
// Package rootcerts contains functions to aid in loading CA certificates for
// TLS connections.
//
// In addition, its default behavior on Darwin works around an open issue [1]
// in Go's crypto/x509 that prevents certicates from being loaded from the
// System or Login keychains.
//
// [1] https://github.com/golang/go/issues/14514
package rootcerts
+5
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
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=
+103
View File
@@ -0,0 +1,103 @@
package rootcerts
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
// Config determines where LoadCACerts will load certificates from. When both
// CAFile and CAPath are blank, this library's functions will either load
// system roots explicitly and return them, or set the CertPool to nil to allow
// Go's standard library to load system certs.
type Config struct {
// CAFile is a path to a PEM-encoded certificate file or bundle. Takes
// precedence over CAPath.
CAFile string
// CAPath is a path to a directory populated with PEM-encoded certificates.
CAPath string
}
// ConfigureTLS sets up the RootCAs on the provided tls.Config based on the
// Config specified.
func ConfigureTLS(t *tls.Config, c *Config) error {
if t == nil {
return nil
}
pool, err := LoadCACerts(c)
if err != nil {
return err
}
t.RootCAs = pool
return nil
}
// LoadCACerts loads a CertPool based on the Config specified.
func LoadCACerts(c *Config) (*x509.CertPool, error) {
if c == nil {
c = &Config{}
}
if c.CAFile != "" {
return LoadCAFile(c.CAFile)
}
if c.CAPath != "" {
return LoadCAPath(c.CAPath)
}
return LoadSystemCAs()
}
// LoadCAFile loads a single PEM-encoded file from the path specified.
func LoadCAFile(caFile string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
pem, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("Error loading CA File: %s", err)
}
ok := pool.AppendCertsFromPEM(pem)
if !ok {
return nil, fmt.Errorf("Error loading CA File: Couldn't parse PEM in: %s", caFile)
}
return pool, nil
}
// LoadCAPath walks the provided path and loads all certificates encounted into
// a pool.
func LoadCAPath(caPath string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
pem, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("Error loading file from CAPath: %s", err)
}
ok := pool.AppendCertsFromPEM(pem)
if !ok {
return fmt.Errorf("Error loading CA Path: Couldn't parse PEM in: %s", path)
}
return nil
}
err := filepath.Walk(caPath, walkFn)
if err != nil {
return nil, err
}
return pool, nil
}
+12
View File
@@ -0,0 +1,12 @@
// +build !darwin
package rootcerts
import "crypto/x509"
// LoadSystemCAs does nothing on non-Darwin systems. We return nil so that
// default behavior of standard TLS config libraries is triggered, which is to
// load system certs.
func LoadSystemCAs() (*x509.CertPool, error) {
return nil, nil
}
+48
View File
@@ -0,0 +1,48 @@
package rootcerts
import (
"crypto/x509"
"os/exec"
"path"
"github.com/mitchellh/go-homedir"
)
// LoadSystemCAs has special behavior on Darwin systems to work around
func LoadSystemCAs() (*x509.CertPool, error) {
pool := x509.NewCertPool()
for _, keychain := range certKeychains() {
err := addCertsFromKeychain(pool, keychain)
if err != nil {
return nil, err
}
}
return pool, nil
}
func addCertsFromKeychain(pool *x509.CertPool, keychain string) error {
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", keychain)
data, err := cmd.Output()
if err != nil {
return err
}
pool.AppendCertsFromPEM(data)
return nil
}
func certKeychains() []string {
keychains := []string{
"/System/Library/Keychains/SystemRootCertificates.keychain",
"/Library/Keychains/System.keychain",
}
home, err := homedir.Dir()
if err == nil {
loginKeychain := path.Join(home, "Library", "Keychains", "login.keychain")
keychains = append(keychains, loginKeychain)
}
return keychains
}
+26
View File
@@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.cover.out*
coverage.html
+65
View File
@@ -0,0 +1,65 @@
TOOLS= golang.org/x/tools/cover
GOCOVER_TMPFILE?= $(GOCOVER_FILE).tmp
GOCOVER_FILE?= .cover.out
GOCOVERHTML?= coverage.html
FIND=`/usr/bin/which 2> /dev/null gfind find | /usr/bin/grep -v ^no | /usr/bin/head -n 1`
XARGS=`/usr/bin/which 2> /dev/null gxargs xargs | /usr/bin/grep -v ^no | /usr/bin/head -n 1`
test:: $(GOCOVER_FILE)
@$(MAKE) -C cmd/sockaddr test
cover:: coverage_report
$(GOCOVER_FILE)::
@${FIND} . -type d ! -path '*cmd*' ! -path '*.git*' -print0 | ${XARGS} -0 -I % sh -ec "cd % && rm -f $(GOCOVER_TMPFILE) && go test -coverprofile=$(GOCOVER_TMPFILE)"
@echo 'mode: set' > $(GOCOVER_FILE)
@${FIND} . -type f ! -path '*cmd*' ! -path '*.git*' -name "$(GOCOVER_TMPFILE)" -print0 | ${XARGS} -0 -n1 cat $(GOCOVER_TMPFILE) | grep -v '^mode: ' >> ${PWD}/$(GOCOVER_FILE)
$(GOCOVERHTML): $(GOCOVER_FILE)
go tool cover -html=$(GOCOVER_FILE) -o $(GOCOVERHTML)
coverage_report:: $(GOCOVER_FILE)
go tool cover -html=$(GOCOVER_FILE)
audit_tools::
@go get -u github.com/golang/lint/golint && echo "Installed golint:"
@go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:"
@go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:"
@go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:"
@go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:"
audit::
deadcode
go tool vet -all *.go
go tool vet -shadow=true *.go
golint *.go
ineffassign .
gocyclo -over 65 *.go
misspell *.go
clean::
rm -f $(GOCOVER_FILE) $(GOCOVERHTML)
dev::
@go build
@$(MAKE) -B -C cmd/sockaddr sockaddr
install::
@go install
@$(MAKE) -C cmd/sockaddr install
doc::
@echo Visit: http://127.0.0.1:6161/pkg/github.com/hashicorp/go-sockaddr/
godoc -http=:6161 -goroot $GOROOT
world::
@set -e; \
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; \
done; \
done
$(MAKE) -C cmd/sockaddr world
+373
View File
@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
+118
View File
@@ -0,0 +1,118 @@
# go-sockaddr
## `sockaddr` Library
Socket address convenience functions for Go. `go-sockaddr` is a convenience
library that makes doing the right thing with IP addresses easy. `go-sockaddr`
is loosely modeled after the UNIX `sockaddr_t` and creates a union of the family
of `sockaddr_t` types (see below for an ascii diagram). Library documentation
is available
at
[https://godoc.org/github.com/hashicorp/go-sockaddr](https://godoc.org/github.com/hashicorp/go-sockaddr).
The primary intent of the library was to make it possible to define heuristics
for selecting the correct IP addresses when a configuration is evaluated at
runtime. See
the
[docs](https://godoc.org/github.com/hashicorp/go-sockaddr),
[`template` package](https://godoc.org/github.com/hashicorp/go-sockaddr/template),
tests,
and
[CLI utility](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr)
for details and hints as to how to use this library.
For example, with this library it is possible to find an IP address that:
* is attached to a default route
([`GetDefaultInterfaces()`](https://godoc.org/github.com/hashicorp/go-sockaddr#GetDefaultInterfaces))
* is contained within a CIDR block ([`IfByNetwork()`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByNetwork))
* is an RFC1918 address
([`IfByRFC("1918")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC))
* is ordered
([`OrderedIfAddrBy(args)`](https://godoc.org/github.com/hashicorp/go-sockaddr#OrderedIfAddrBy) where
`args` includes, but is not limited
to,
[`AscIfType`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscIfType),
[`AscNetworkSize`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscNetworkSize))
* excludes all IPv6 addresses
([`IfByType("^(IPv4)$")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByType))
* is larger than a `/32`
([`IfByMaskSize(32)`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByMaskSize))
* is not on a `down` interface
([`ExcludeIfs("flags", "down")`](https://godoc.org/github.com/hashicorp/go-sockaddr#ExcludeIfs))
* preferences an IPv6 address over an IPv4 address
([`SortIfByType()`](https://godoc.org/github.com/hashicorp/go-sockaddr#SortIfByType) +
[`ReverseIfAddrs()`](https://godoc.org/github.com/hashicorp/go-sockaddr#ReverseIfAddrs)); and
* excludes any IP in RFC6890 address
([`IfByRFC("6890")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC))
Or any combination or variation therein.
There are also a few simple helper functions such as `GetPublicIP` and
`GetPrivateIP` which both return strings and select the first public or private
IP address on the default interface, respectively. Similarly, there is also a
helper function called `GetInterfaceIP` which returns the first usable IP
address on the named interface.
## `sockaddr` CLI
Given the possible complexity of the `sockaddr` library, there is a CLI utility
that accompanies the library, also
called
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr).
The
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr)
utility exposes nearly all of the functionality of the library and can be used
either as an administrative tool or testing tool. To install
the
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr),
run:
```text
$ go get -u github.com/hashicorp/go-sockaddr/cmd/sockaddr
```
If you're familiar with UNIX's `sockaddr` struct's, the following diagram
mapping the C `sockaddr` (top) to `go-sockaddr` structs (bottom) and
interfaces will be helpful:
```
+-------------------------------------------------------+
| |
| sockaddr |
| SockAddr |
| |
| +--------------+ +----------------------------------+ |
| | sockaddr_un | | | |
| | SockAddrUnix | | sockaddr_in{,6} | |
| +--------------+ | IPAddr | |
| | | |
| | +-------------+ +--------------+ | |
| | | sockaddr_in | | sockaddr_in6 | | |
| | | IPv4Addr | | IPv6Addr | | |
| | +-------------+ +--------------+ | |
| | | |
| +----------------------------------+ |
| |
+-------------------------------------------------------+
```
## Inspiration and Design
There were many subtle inspirations that led to this design, but the most direct
inspiration for the filtering syntax was
OpenBSD's
[`pf.conf(5)`](https://www.freebsd.org/cgi/man.cgi?query=pf.conf&apropos=0&sektion=0&arch=default&format=html#PARAMETERS) firewall
syntax that lets you select the first IP address on a given named interface.
The original problem stemmed from:
* needing to create immutable images using [Packer](https://www.packer.io) that
ran the [Consul](https://www.consul.io) process (Consul can only use one IP
address at a time);
* images that may or may not have multiple interfaces or IP addresses at
runtime; and
* we didn't want to rely on configuration management to render out the correct
IP address if the VM image was being used in an auto-scaling group.
Instead we needed some way to codify a heuristic that would correctly select the
right IP address but the input parameters were not known when the image was
created.
+5
View File
@@ -0,0 +1,5 @@
/*
Package sockaddr is a Go implementation of the UNIX socket family data types and
related helper functions.
*/
package sockaddr
+8
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
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=
+254
View File
@@ -0,0 +1,254 @@
package sockaddr
import "strings"
// ifAddrAttrMap is a map of the IfAddr type-specific attributes.
var ifAddrAttrMap map[AttrName]func(IfAddr) string
var ifAddrAttrs []AttrName
func init() {
ifAddrAttrInit()
}
// GetPrivateIP returns a string with a single IP address that is part of RFC
// 6890 and has a default route. If the system can't determine its IP address
// or find an RFC 6890 IP address, an empty string will be returned instead.
// This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetPrivateInterfaces | attr "address"}}'
/// ```
func GetPrivateIP() (string, error) {
privateIfs, err := GetPrivateInterfaces()
if err != nil {
return "", err
}
if len(privateIfs) < 1 {
return "", nil
}
ifAddr := privateIfs[0]
ip := *ToIPAddr(ifAddr.SockAddr)
return ip.NetIP().String(), nil
}
// GetPrivateIPs returns a string with all IP addresses that are part of RFC
// 6890 (regardless of whether or not there is a default route, unlike
// GetPublicIP). If the system can't find any RFC 6890 IP addresses, an empty
// string will be returned instead. This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | include "RFC" "6890" | join "address" " "}}'
/// ```
func GetPrivateIPs() (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
} else if len(ifAddrs) < 1 {
return "", nil
}
ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP)
if len(ifAddrs) == 0 {
return "", nil
}
OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs)
ifAddrs, _, err = IfByRFC("6890", ifAddrs)
if err != nil {
return "", err
} else if len(ifAddrs) == 0 {
return "", nil
}
_, ifAddrs, err = IfByRFC(ForwardingBlacklistRFC, ifAddrs)
if err != nil {
return "", err
} else if len(ifAddrs) == 0 {
return "", nil
}
ips := make([]string, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
ip := *ToIPAddr(ifAddr.SockAddr)
s := ip.NetIP().String()
ips = append(ips, s)
}
return strings.Join(ips, " "), nil
}
// GetPublicIP returns a string with a single IP address that is NOT part of RFC
// 6890 and has a default route. If the system can't determine its IP address
// or find a non RFC 6890 IP address, an empty string will be returned instead.
// This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetPublicInterfaces | attr "address"}}'
/// ```
func GetPublicIP() (string, error) {
publicIfs, err := GetPublicInterfaces()
if err != nil {
return "", err
} else if len(publicIfs) < 1 {
return "", nil
}
ifAddr := publicIfs[0]
ip := *ToIPAddr(ifAddr.SockAddr)
return ip.NetIP().String(), nil
}
// GetPublicIPs returns a string with all IP addresses that are NOT part of RFC
// 6890 (regardless of whether or not there is a default route, unlike
// GetPublicIP). If the system can't find any non RFC 6890 IP addresses, an
// empty string will be returned instead. This function is the `eval`
// equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | exclude "RFC" "6890" | join "address" " "}}'
/// ```
func GetPublicIPs() (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
} else if len(ifAddrs) < 1 {
return "", nil
}
ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP)
if len(ifAddrs) == 0 {
return "", nil
}
OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs)
_, ifAddrs, err = IfByRFC("6890", ifAddrs)
if err != nil {
return "", err
} else if len(ifAddrs) == 0 {
return "", nil
}
ips := make([]string, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
ip := *ToIPAddr(ifAddr.SockAddr)
s := ip.NetIP().String()
ips = append(ips, s)
}
return strings.Join(ips, " "), nil
}
// GetInterfaceIP returns a string with a single IP address sorted by the size
// of the network (i.e. IP addresses with a smaller netmask, larger network
// size, are sorted first). This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | include "name" <<ARG>> | sort "type,size" | include "flag" "forwardable" | attr "address" }}'
/// ```
func GetInterfaceIP(namedIfRE string) (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
}
ifAddrs, _, err = IfByName(namedIfRE, ifAddrs)
if err != nil {
return "", err
}
ifAddrs, _, err = IfByFlag("forwardable", ifAddrs)
if err != nil {
return "", err
}
ifAddrs, err = SortIfBy("+type,+size", ifAddrs)
if err != nil {
return "", err
}
if len(ifAddrs) == 0 {
return "", err
}
ip := ToIPAddr(ifAddrs[0].SockAddr)
if ip == nil {
return "", err
}
return IPAddrAttr(*ip, "address"), nil
}
// GetInterfaceIPs returns a string with all IPs, sorted by the size of the
// network (i.e. IP addresses with a smaller netmask, larger network size, are
// sorted first), on a named interface. This function is the `eval` equivalent
// of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | include "name" <<ARG>> | sort "type,size" | join "address" " "}}'
/// ```
func GetInterfaceIPs(namedIfRE string) (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
}
ifAddrs, _, err = IfByName(namedIfRE, ifAddrs)
if err != nil {
return "", err
}
ifAddrs, err = SortIfBy("+type,+size", ifAddrs)
if err != nil {
return "", err
}
if len(ifAddrs) == 0 {
return "", err
}
ips := make([]string, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
ip := *ToIPAddr(ifAddr.SockAddr)
s := ip.NetIP().String()
ips = append(ips, s)
}
return strings.Join(ips, " "), nil
}
// IfAddrAttrs returns a list of attributes supported by the IfAddr type
func IfAddrAttrs() []AttrName {
return ifAddrAttrs
}
// IfAddrAttr returns a string representation of an attribute for the given
// IfAddr.
func IfAddrAttr(ifAddr IfAddr, attrName AttrName) string {
fn, found := ifAddrAttrMap[attrName]
if !found {
return ""
}
return fn(ifAddr)
}
// ifAddrAttrInit is called once at init()
func ifAddrAttrInit() {
// Sorted for human readability
ifAddrAttrs = []AttrName{
"flags",
"name",
}
ifAddrAttrMap = map[AttrName]func(ifAddr IfAddr) string{
"flags": func(ifAddr IfAddr) string {
return ifAddr.Interface.Flags.String()
},
"name": func(ifAddr IfAddr) string {
return ifAddr.Interface.Name
},
}
}
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
package sockaddr
import (
"fmt"
"net"
)
// IfAddr is a union of a SockAddr and a net.Interface.
type IfAddr struct {
SockAddr
net.Interface
}
// Attr returns the named attribute as a string
func (ifAddr IfAddr) Attr(attrName AttrName) (string, error) {
val := IfAddrAttr(ifAddr, attrName)
if val != "" {
return val, nil
}
return Attr(ifAddr.SockAddr, attrName)
}
// Attr returns the named attribute as a string
func Attr(sa SockAddr, attrName AttrName) (string, error) {
switch sockType := sa.Type(); {
case sockType&TypeIP != 0:
ip := *ToIPAddr(sa)
attrVal := IPAddrAttr(ip, attrName)
if attrVal != "" {
return attrVal, nil
}
if sockType == TypeIPv4 {
ipv4 := *ToIPv4Addr(sa)
attrVal := IPv4AddrAttr(ipv4, attrName)
if attrVal != "" {
return attrVal, nil
}
} else if sockType == TypeIPv6 {
ipv6 := *ToIPv6Addr(sa)
attrVal := IPv6AddrAttr(ipv6, attrName)
if attrVal != "" {
return attrVal, nil
}
}
case sockType == TypeUnix:
us := *ToUnixSock(sa)
attrVal := UnixSockAttr(us, attrName)
if attrVal != "" {
return attrVal, nil
}
}
// Non type-specific attributes
switch attrName {
case "string":
return sa.String(), nil
case "type":
return sa.Type().String(), nil
}
return "", fmt.Errorf("unsupported attribute name %q", attrName)
}
+169
View File
@@ -0,0 +1,169 @@
package sockaddr
import (
"fmt"
"math/big"
"net"
"strings"
)
// Constants for the sizes of IPv3, IPv4, and IPv6 address types.
const (
IPv3len = 6
IPv4len = 4
IPv6len = 16
)
// IPAddr is a generic IP address interface for IPv4 and IPv6 addresses,
// networks, and socket endpoints.
type IPAddr interface {
SockAddr
AddressBinString() string
AddressHexString() string
Cmp(SockAddr) int
CmpAddress(SockAddr) int
CmpPort(SockAddr) int
FirstUsable() IPAddr
Host() IPAddr
IPPort() IPPort
LastUsable() IPAddr
Maskbits() int
NetIP() *net.IP
NetIPMask() *net.IPMask
NetIPNet() *net.IPNet
Network() IPAddr
Octets() []int
}
// IPPort is the type for an IP port number for the TCP and UDP IP transports.
type IPPort uint16
// IPPrefixLen is a typed integer representing the prefix length for a given
// IPAddr.
type IPPrefixLen byte
// ipAddrAttrMap is a map of the IPAddr type-specific attributes.
var ipAddrAttrMap map[AttrName]func(IPAddr) string
var ipAddrAttrs []AttrName
func init() {
ipAddrInit()
}
// NewIPAddr creates a new IPAddr from a string. Returns nil if the string is
// not an IPv4 or an IPv6 address.
func NewIPAddr(addr string) (IPAddr, error) {
ipv4Addr, err := NewIPv4Addr(addr)
if err == nil {
return ipv4Addr, nil
}
ipv6Addr, err := NewIPv6Addr(addr)
if err == nil {
return ipv6Addr, nil
}
return nil, fmt.Errorf("invalid IPAddr %v", addr)
}
// IPAddrAttr returns a string representation of an attribute for the given
// IPAddr.
func IPAddrAttr(ip IPAddr, selector AttrName) string {
fn, found := ipAddrAttrMap[selector]
if !found {
return ""
}
return fn(ip)
}
// IPAttrs returns a list of attributes supported by the IPAddr type
func IPAttrs() []AttrName {
return ipAddrAttrs
}
// MustIPAddr is a helper method that must return an IPAddr or panic on invalid
// input.
func MustIPAddr(addr string) IPAddr {
ip, err := NewIPAddr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPAddr from %+q: %v", addr, err))
}
return ip
}
// ipAddrInit is called once at init()
func ipAddrInit() {
// Sorted for human readability
ipAddrAttrs = []AttrName{
"host",
"address",
"port",
"netmask",
"network",
"mask_bits",
"binary",
"hex",
"first_usable",
"last_usable",
"octets",
}
ipAddrAttrMap = map[AttrName]func(ip IPAddr) string{
"address": func(ip IPAddr) string {
return ip.NetIP().String()
},
"binary": func(ip IPAddr) string {
return ip.AddressBinString()
},
"first_usable": func(ip IPAddr) string {
return ip.FirstUsable().String()
},
"hex": func(ip IPAddr) string {
return ip.AddressHexString()
},
"host": func(ip IPAddr) string {
return ip.Host().String()
},
"last_usable": func(ip IPAddr) string {
return ip.LastUsable().String()
},
"mask_bits": func(ip IPAddr) string {
return fmt.Sprintf("%d", ip.Maskbits())
},
"netmask": func(ip IPAddr) string {
switch v := ip.(type) {
case IPv4Addr:
ipv4Mask := IPv4Addr{
Address: IPv4Address(v.Mask),
Mask: IPv4HostMask,
}
return ipv4Mask.String()
case IPv6Addr:
ipv6Mask := new(big.Int)
ipv6Mask.Set(v.Mask)
ipv6MaskAddr := IPv6Addr{
Address: IPv6Address(ipv6Mask),
Mask: ipv6HostMask,
}
return ipv6MaskAddr.String()
default:
return fmt.Sprintf("<unsupported type: %T>", ip)
}
},
"network": func(ip IPAddr) string {
return ip.Network().NetIP().String()
},
"octets": func(ip IPAddr) string {
octets := ip.Octets()
octetStrs := make([]string, 0, len(octets))
for _, octet := range octets {
octetStrs = append(octetStrs, fmt.Sprintf("%d", octet))
}
return strings.Join(octetStrs, " ")
},
"port": func(ip IPAddr) string {
return fmt.Sprintf("%d", ip.IPPort())
},
}
}
+98
View File
@@ -0,0 +1,98 @@
package sockaddr
import "bytes"
type IPAddrs []IPAddr
func (s IPAddrs) Len() int { return len(s) }
func (s IPAddrs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// // SortIPAddrsByCmp is a type that satisfies sort.Interface and can be used
// // by the routines in this package. The SortIPAddrsByCmp type is used to
// // sort IPAddrs by Cmp()
// type SortIPAddrsByCmp struct{ IPAddrs }
// // Less reports whether the element with index i should sort before the
// // element with index j.
// func (s SortIPAddrsByCmp) Less(i, j int) bool {
// // Sort by Type, then address, then port number.
// return Less(s.IPAddrs[i], s.IPAddrs[j])
// }
// SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and
// can be used by the routines in this package. The
// SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest
// network (most specific to largest network).
type SortIPAddrsByNetworkSize struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsByNetworkSize) Less(i, j int) bool {
// Sort masks with a larger binary value (i.e. fewer hosts per network
// prefix) after masks with a smaller value (larger number of hosts per
// prefix).
switch bytes.Compare([]byte(*s.IPAddrs[i].NetIPMask()), []byte(*s.IPAddrs[j].NetIPMask())) {
case 0:
// Fall through to the second test if the net.IPMasks are the
// same.
break
case 1:
return true
case -1:
return false
default:
panic("bad, m'kay?")
}
// Sort IPs based on the length (i.e. prefer IPv4 over IPv6).
iLen := len(*s.IPAddrs[i].NetIP())
jLen := len(*s.IPAddrs[j].NetIP())
if iLen != jLen {
return iLen > jLen
}
// Sort IPs based on their network address from lowest to highest.
switch bytes.Compare(s.IPAddrs[i].NetIPNet().IP, s.IPAddrs[j].NetIPNet().IP) {
case 0:
break
case 1:
return false
case -1:
return true
default:
panic("lol wut?")
}
// If a host does not have a port set, it always sorts after hosts
// that have a port (e.g. a host with a /32 and port number is more
// specific and should sort first over a host with a /32 but no port
// set).
if s.IPAddrs[i].IPPort() == 0 || s.IPAddrs[j].IPPort() == 0 {
return false
}
return s.IPAddrs[i].IPPort() < s.IPAddrs[j].IPPort()
}
// SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and
// can be used by the routines in this package. The
// SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest
// network (most specific to largest network).
type SortIPAddrsBySpecificMaskLen struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsBySpecificMaskLen) Less(i, j int) bool {
return s.IPAddrs[i].Maskbits() > s.IPAddrs[j].Maskbits()
}
// SortIPAddrsByBroadMaskLen is a type that satisfies sort.Interface and can
// be used by the routines in this package. The SortIPAddrsByBroadMaskLen
// type is used to sort IPAddrs by largest network (i.e. largest subnets
// first).
type SortIPAddrsByBroadMaskLen struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsByBroadMaskLen) Less(i, j int) bool {
return s.IPAddrs[i].Maskbits() < s.IPAddrs[j].Maskbits()
}
+516
View File
@@ -0,0 +1,516 @@
package sockaddr
import (
"encoding/binary"
"fmt"
"net"
"regexp"
"strconv"
"strings"
)
type (
// IPv4Address is a named type representing an IPv4 address.
IPv4Address uint32
// IPv4Network is a named type representing an IPv4 network.
IPv4Network uint32
// IPv4Mask is a named type representing an IPv4 network mask.
IPv4Mask uint32
)
// IPv4HostMask is a constant represents a /32 IPv4 Address
// (i.e. 255.255.255.255).
const IPv4HostMask = IPv4Mask(0xffffffff)
// ipv4AddrAttrMap is a map of the IPv4Addr type-specific attributes.
var ipv4AddrAttrMap map[AttrName]func(IPv4Addr) string
var ipv4AddrAttrs []AttrName
var trailingHexNetmaskRE *regexp.Regexp
// IPv4Addr implements a convenience wrapper around the union of Go's
// built-in net.IP and net.IPNet types. In UNIX-speak, IPv4Addr implements
// `sockaddr` when the the address family is set to AF_INET
// (i.e. `sockaddr_in`).
type IPv4Addr struct {
IPAddr
Address IPv4Address
Mask IPv4Mask
Port IPPort
}
func init() {
ipv4AddrInit()
trailingHexNetmaskRE = regexp.MustCompile(`/([0f]{8})$`)
}
// NewIPv4Addr creates an IPv4Addr from a string. String can be in the form
// of either an IPv4:port (e.g. `1.2.3.4:80`, in which case the mask is
// assumed to be a `/32`), an IPv4 address (e.g. `1.2.3.4`, also with a `/32`
// mask), or an IPv4 CIDR (e.g. `1.2.3.4/24`, which has its IP port
// initialized to zero). ipv4Str can not be a hostname.
//
// NOTE: Many net.*() routines will initialize and return an IPv6 address.
// To create uint32 values from net.IP, always test to make sure the address
// returned can be converted to a 4 byte array using To4().
func NewIPv4Addr(ipv4Str string) (IPv4Addr, error) {
// Strip off any bogus hex-encoded netmasks that will be mis-parsed by Go. In
// particular, clients with the Barracuda VPN client will see something like:
// `192.168.3.51/00ffffff` as their IP address.
trailingHexNetmaskRe := trailingHexNetmaskRE.Copy()
if match := trailingHexNetmaskRe.FindStringIndex(ipv4Str); match != nil {
ipv4Str = ipv4Str[:match[0]]
}
// Parse as an IPv4 CIDR
ipAddr, network, err := net.ParseCIDR(ipv4Str)
if err == nil {
ipv4 := ipAddr.To4()
if ipv4 == nil {
return IPv4Addr{}, fmt.Errorf("Unable to convert %s to an IPv4 address", ipv4Str)
}
// If we see an IPv6 netmask, convert it to an IPv4 mask.
netmaskSepPos := strings.LastIndexByte(ipv4Str, '/')
if netmaskSepPos != -1 && netmaskSepPos+1 < len(ipv4Str) {
netMask, err := strconv.ParseUint(ipv4Str[netmaskSepPos+1:], 10, 8)
if err != nil {
return IPv4Addr{}, fmt.Errorf("Unable to convert %s to an IPv4 address: unable to parse CIDR netmask: %v", ipv4Str, err)
} else if netMask > 128 {
return IPv4Addr{}, fmt.Errorf("Unable to convert %s to an IPv4 address: invalid CIDR netmask", ipv4Str)
}
if netMask >= 96 {
// Convert the IPv6 netmask to an IPv4 netmask
network.Mask = net.CIDRMask(int(netMask-96), IPv4len*8)
}
}
ipv4Addr := IPv4Addr{
Address: IPv4Address(binary.BigEndian.Uint32(ipv4)),
Mask: IPv4Mask(binary.BigEndian.Uint32(network.Mask)),
}
return ipv4Addr, nil
}
// Attempt to parse ipv4Str as a /32 host with a port number.
tcpAddr, err := net.ResolveTCPAddr("tcp4", ipv4Str)
if err == nil {
ipv4 := tcpAddr.IP.To4()
if ipv4 == nil {
return IPv4Addr{}, fmt.Errorf("Unable to resolve %+q as an IPv4 address", ipv4Str)
}
ipv4Uint32 := binary.BigEndian.Uint32(ipv4)
ipv4Addr := IPv4Addr{
Address: IPv4Address(ipv4Uint32),
Mask: IPv4HostMask,
Port: IPPort(tcpAddr.Port),
}
return ipv4Addr, nil
}
// Parse as a naked IPv4 address
ip := net.ParseIP(ipv4Str)
if ip != nil {
ipv4 := ip.To4()
if ipv4 == nil {
return IPv4Addr{}, fmt.Errorf("Unable to string convert %+q to an IPv4 address", ipv4Str)
}
ipv4Uint32 := binary.BigEndian.Uint32(ipv4)
ipv4Addr := IPv4Addr{
Address: IPv4Address(ipv4Uint32),
Mask: IPv4HostMask,
}
return ipv4Addr, nil
}
return IPv4Addr{}, fmt.Errorf("Unable to parse %+q to an IPv4 address: %v", ipv4Str, err)
}
// AddressBinString returns a string with the IPv4Addr's Address represented
// as a sequence of '0' and '1' characters. This method is useful for
// debugging or by operators who want to inspect an address.
func (ipv4 IPv4Addr) AddressBinString() string {
return fmt.Sprintf("%032s", strconv.FormatUint(uint64(ipv4.Address), 2))
}
// AddressHexString returns a string with the IPv4Addr address represented as
// a sequence of hex characters. This method is useful for debugging or by
// operators who want to inspect an address.
func (ipv4 IPv4Addr) AddressHexString() string {
return fmt.Sprintf("%08s", strconv.FormatUint(uint64(ipv4.Address), 16))
}
// Broadcast is an IPv4Addr-only method that returns the broadcast address of
// the network.
//
// NOTE: IPv6 only supports multicast, so this method only exists for
// IPv4Addr.
func (ipv4 IPv4Addr) Broadcast() IPAddr {
// Nothing should listen on a broadcast address.
return IPv4Addr{
Address: IPv4Address(ipv4.BroadcastAddress()),
Mask: IPv4HostMask,
}
}
// BroadcastAddress returns a IPv4Network of the IPv4Addr's broadcast
// address.
func (ipv4 IPv4Addr) BroadcastAddress() IPv4Network {
return IPv4Network(uint32(ipv4.Address)&uint32(ipv4.Mask) | ^uint32(ipv4.Mask))
}
// CmpAddress follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its address is lower than arg
// - 0 if the SockAddr arg is equal to the receiving IPv4Addr or the argument is
// of a different type.
// - 1 If the argument should sort first.
func (ipv4 IPv4Addr) CmpAddress(sa SockAddr) int {
ipv4b, ok := sa.(IPv4Addr)
if !ok {
return sortDeferDecision
}
switch {
case ipv4.Address == ipv4b.Address:
return sortDeferDecision
case ipv4.Address < ipv4b.Address:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// CmpPort follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its port is lower than arg
// - 0 if the SockAddr arg's port number is equal to the receiving IPv4Addr,
// regardless of type.
// - 1 If the argument should sort first.
func (ipv4 IPv4Addr) CmpPort(sa SockAddr) int {
var saPort IPPort
switch v := sa.(type) {
case IPv4Addr:
saPort = v.Port
case IPv6Addr:
saPort = v.Port
default:
return sortDeferDecision
}
switch {
case ipv4.Port == saPort:
return sortDeferDecision
case ipv4.Port < saPort:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// CmpRFC follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because it belongs to the RFC and its
// arg does not
// - 0 if the receiver and arg both belong to the same RFC or neither do.
// - 1 If the arg belongs to the RFC but receiver does not.
func (ipv4 IPv4Addr) CmpRFC(rfcNum uint, sa SockAddr) int {
recvInRFC := IsRFC(rfcNum, ipv4)
ipv4b, ok := sa.(IPv4Addr)
if !ok {
// If the receiver is part of the desired RFC and the SockAddr
// argument is not, return -1 so that the receiver sorts before
// the non-IPv4 SockAddr. Conversely, if the receiver is not
// part of the RFC, punt on sorting and leave it for the next
// sorter.
if recvInRFC {
return sortReceiverBeforeArg
} else {
return sortDeferDecision
}
}
argInRFC := IsRFC(rfcNum, ipv4b)
switch {
case (recvInRFC && argInRFC), (!recvInRFC && !argInRFC):
// If a and b both belong to the RFC, or neither belong to
// rfcNum, defer sorting to the next sorter.
return sortDeferDecision
case recvInRFC && !argInRFC:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// Contains returns true if the SockAddr is contained within the receiver.
func (ipv4 IPv4Addr) Contains(sa SockAddr) bool {
ipv4b, ok := sa.(IPv4Addr)
if !ok {
return false
}
return ipv4.ContainsNetwork(ipv4b)
}
// ContainsAddress returns true if the IPv4Address is contained within the
// receiver.
func (ipv4 IPv4Addr) ContainsAddress(x IPv4Address) bool {
return IPv4Address(ipv4.NetworkAddress()) <= x &&
IPv4Address(ipv4.BroadcastAddress()) >= x
}
// ContainsNetwork returns true if the network from IPv4Addr is contained
// within the receiver.
func (ipv4 IPv4Addr) ContainsNetwork(x IPv4Addr) bool {
return ipv4.NetworkAddress() <= x.NetworkAddress() &&
ipv4.BroadcastAddress() >= x.BroadcastAddress()
}
// DialPacketArgs returns the arguments required to be passed to
// net.DialUDP(). If the Mask of ipv4 is not a /32 or the Port is 0,
// DialPacketArgs() will fail. See Host() to create an IPv4Addr with its
// mask set to /32.
func (ipv4 IPv4Addr) DialPacketArgs() (network, dialArgs string) {
if ipv4.Mask != IPv4HostMask || ipv4.Port == 0 {
return "udp4", ""
}
return "udp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// DialStreamArgs returns the arguments required to be passed to
// net.DialTCP(). If the Mask of ipv4 is not a /32 or the Port is 0,
// DialStreamArgs() will fail. See Host() to create an IPv4Addr with its
// mask set to /32.
func (ipv4 IPv4Addr) DialStreamArgs() (network, dialArgs string) {
if ipv4.Mask != IPv4HostMask || ipv4.Port == 0 {
return "tcp4", ""
}
return "tcp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// Equal returns true if a SockAddr is equal to the receiving IPv4Addr.
func (ipv4 IPv4Addr) Equal(sa SockAddr) bool {
ipv4b, ok := sa.(IPv4Addr)
if !ok {
return false
}
if ipv4.Port != ipv4b.Port {
return false
}
if ipv4.Address != ipv4b.Address {
return false
}
if ipv4.NetIPNet().String() != ipv4b.NetIPNet().String() {
return false
}
return true
}
// FirstUsable returns an IPv4Addr set to the first address following the
// network prefix. The first usable address in a network is normally the
// gateway and should not be used except by devices forwarding packets
// between two administratively distinct networks (i.e. a router). This
// function does not discriminate against first usable vs "first address that
// should be used." For example, FirstUsable() on "192.168.1.10/24" would
// return the address "192.168.1.1/24".
func (ipv4 IPv4Addr) FirstUsable() IPAddr {
addr := ipv4.NetworkAddress()
// If /32, return the address itself. If /31 assume a point-to-point
// link and return the lower address.
if ipv4.Maskbits() < 31 {
addr++
}
return IPv4Addr{
Address: IPv4Address(addr),
Mask: IPv4HostMask,
}
}
// Host returns a copy of ipv4 with its mask set to /32 so that it can be
// used by DialPacketArgs(), DialStreamArgs(), ListenPacketArgs(), or
// ListenStreamArgs().
func (ipv4 IPv4Addr) Host() IPAddr {
// Nothing should listen on a broadcast address.
return IPv4Addr{
Address: ipv4.Address,
Mask: IPv4HostMask,
Port: ipv4.Port,
}
}
// IPPort returns the Port number attached to the IPv4Addr
func (ipv4 IPv4Addr) IPPort() IPPort {
return ipv4.Port
}
// LastUsable returns the last address before the broadcast address in a
// given network.
func (ipv4 IPv4Addr) LastUsable() IPAddr {
addr := ipv4.BroadcastAddress()
// If /32, return the address itself. If /31 assume a point-to-point
// link and return the upper address.
if ipv4.Maskbits() < 31 {
addr--
}
return IPv4Addr{
Address: IPv4Address(addr),
Mask: IPv4HostMask,
}
}
// ListenPacketArgs returns the arguments required to be passed to
// net.ListenUDP(). If the Mask of ipv4 is not a /32, ListenPacketArgs()
// will fail. See Host() to create an IPv4Addr with its mask set to /32.
func (ipv4 IPv4Addr) ListenPacketArgs() (network, listenArgs string) {
if ipv4.Mask != IPv4HostMask {
return "udp4", ""
}
return "udp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// ListenStreamArgs returns the arguments required to be passed to
// net.ListenTCP(). If the Mask of ipv4 is not a /32, ListenStreamArgs()
// will fail. See Host() to create an IPv4Addr with its mask set to /32.
func (ipv4 IPv4Addr) ListenStreamArgs() (network, listenArgs string) {
if ipv4.Mask != IPv4HostMask {
return "tcp4", ""
}
return "tcp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// Maskbits returns the number of network mask bits in a given IPv4Addr. For
// example, the Maskbits() of "192.168.1.1/24" would return 24.
func (ipv4 IPv4Addr) Maskbits() int {
mask := make(net.IPMask, IPv4len)
binary.BigEndian.PutUint32(mask, uint32(ipv4.Mask))
maskOnes, _ := mask.Size()
return maskOnes
}
// MustIPv4Addr is a helper method that must return an IPv4Addr or panic on
// invalid input.
func MustIPv4Addr(addr string) IPv4Addr {
ipv4, err := NewIPv4Addr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPv4Addr from %+q: %v", addr, err))
}
return ipv4
}
// NetIP returns the address as a net.IP (address is always presized to
// IPv4).
func (ipv4 IPv4Addr) NetIP() *net.IP {
x := make(net.IP, IPv4len)
binary.BigEndian.PutUint32(x, uint32(ipv4.Address))
return &x
}
// NetIPMask create a new net.IPMask from the IPv4Addr.
func (ipv4 IPv4Addr) NetIPMask() *net.IPMask {
ipv4Mask := net.IPMask{}
ipv4Mask = make(net.IPMask, IPv4len)
binary.BigEndian.PutUint32(ipv4Mask, uint32(ipv4.Mask))
return &ipv4Mask
}
// NetIPNet create a new net.IPNet from the IPv4Addr.
func (ipv4 IPv4Addr) NetIPNet() *net.IPNet {
ipv4net := &net.IPNet{}
ipv4net.IP = make(net.IP, IPv4len)
binary.BigEndian.PutUint32(ipv4net.IP, uint32(ipv4.NetworkAddress()))
ipv4net.Mask = *ipv4.NetIPMask()
return ipv4net
}
// Network returns the network prefix or network address for a given network.
func (ipv4 IPv4Addr) Network() IPAddr {
return IPv4Addr{
Address: IPv4Address(ipv4.NetworkAddress()),
Mask: ipv4.Mask,
}
}
// NetworkAddress returns an IPv4Network of the IPv4Addr's network address.
func (ipv4 IPv4Addr) NetworkAddress() IPv4Network {
return IPv4Network(uint32(ipv4.Address) & uint32(ipv4.Mask))
}
// Octets returns a slice of the four octets in an IPv4Addr's Address. The
// order of the bytes is big endian.
func (ipv4 IPv4Addr) Octets() []int {
return []int{
int(ipv4.Address >> 24),
int((ipv4.Address >> 16) & 0xff),
int((ipv4.Address >> 8) & 0xff),
int(ipv4.Address & 0xff),
}
}
// String returns a string representation of the IPv4Addr
func (ipv4 IPv4Addr) String() string {
if ipv4.Port != 0 {
return fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
if ipv4.Maskbits() == 32 {
return ipv4.NetIP().String()
}
return fmt.Sprintf("%s/%d", ipv4.NetIP().String(), ipv4.Maskbits())
}
// Type is used as a type switch and returns TypeIPv4
func (IPv4Addr) Type() SockAddrType {
return TypeIPv4
}
// IPv4AddrAttr returns a string representation of an attribute for the given
// IPv4Addr.
func IPv4AddrAttr(ipv4 IPv4Addr, selector AttrName) string {
fn, found := ipv4AddrAttrMap[selector]
if !found {
return ""
}
return fn(ipv4)
}
// IPv4Attrs returns a list of attributes supported by the IPv4Addr type
func IPv4Attrs() []AttrName {
return ipv4AddrAttrs
}
// ipv4AddrInit is called once at init()
func ipv4AddrInit() {
// Sorted for human readability
ipv4AddrAttrs = []AttrName{
"size", // Same position as in IPv6 for output consistency
"broadcast",
"uint32",
}
ipv4AddrAttrMap = map[AttrName]func(ipv4 IPv4Addr) string{
"broadcast": func(ipv4 IPv4Addr) string {
return ipv4.Broadcast().String()
},
"size": func(ipv4 IPv4Addr) string {
return fmt.Sprintf("%d", 1<<uint(IPv4len*8-ipv4.Maskbits()))
},
"uint32": func(ipv4 IPv4Addr) string {
return fmt.Sprintf("%d", uint32(ipv4.Address))
},
}
}
+591
View File
@@ -0,0 +1,591 @@
package sockaddr
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"net"
)
type (
// IPv6Address is a named type representing an IPv6 address.
IPv6Address *big.Int
// IPv6Network is a named type representing an IPv6 network.
IPv6Network *big.Int
// IPv6Mask is a named type representing an IPv6 network mask.
IPv6Mask *big.Int
)
// IPv6HostPrefix is a constant represents a /128 IPv6 Prefix.
const IPv6HostPrefix = IPPrefixLen(128)
// ipv6HostMask is an unexported big.Int representing a /128 IPv6 address.
// This value must be a constant and always set to all ones.
var ipv6HostMask IPv6Mask
// ipv6AddrAttrMap is a map of the IPv6Addr type-specific attributes.
var ipv6AddrAttrMap map[AttrName]func(IPv6Addr) string
var ipv6AddrAttrs []AttrName
func init() {
biMask := new(big.Int)
biMask.SetBytes([]byte{
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
},
)
ipv6HostMask = IPv6Mask(biMask)
ipv6AddrInit()
}
// IPv6Addr implements a convenience wrapper around the union of Go's
// built-in net.IP and net.IPNet types. In UNIX-speak, IPv6Addr implements
// `sockaddr` when the the address family is set to AF_INET6
// (i.e. `sockaddr_in6`).
type IPv6Addr struct {
IPAddr
Address IPv6Address
Mask IPv6Mask
Port IPPort
}
// NewIPv6Addr creates an IPv6Addr from a string. String can be in the form of
// an an IPv6:port (e.g. `[2001:4860:0:2001::68]:80`, in which case the mask is
// assumed to be a /128), an IPv6 address (e.g. `2001:4860:0:2001::68`, also
// with a `/128` mask), an IPv6 CIDR (e.g. `2001:4860:0:2001::68/64`, which has
// its IP port initialized to zero). ipv6Str can not be a hostname.
//
// NOTE: Many net.*() routines will initialize and return an IPv4 address.
// Always test to make sure the address returned cannot be converted to a 4 byte
// array using To4().
func NewIPv6Addr(ipv6Str string) (IPv6Addr, error) {
v6Addr := false
LOOP:
for i := 0; i < len(ipv6Str); i++ {
switch ipv6Str[i] {
case '.':
break LOOP
case ':':
v6Addr = true
break LOOP
}
}
if !v6Addr {
return IPv6Addr{}, fmt.Errorf("Unable to resolve %+q as an IPv6 address, appears to be an IPv4 address", ipv6Str)
}
// Attempt to parse ipv6Str as a /128 host with a port number.
tcpAddr, err := net.ResolveTCPAddr("tcp6", ipv6Str)
if err == nil {
ipv6 := tcpAddr.IP.To16()
if ipv6 == nil {
return IPv6Addr{}, fmt.Errorf("Unable to resolve %+q as a 16byte IPv6 address", ipv6Str)
}
ipv6BigIntAddr := new(big.Int)
ipv6BigIntAddr.SetBytes(ipv6)
ipv6BigIntMask := new(big.Int)
ipv6BigIntMask.Set(ipv6HostMask)
ipv6Addr := IPv6Addr{
Address: IPv6Address(ipv6BigIntAddr),
Mask: IPv6Mask(ipv6BigIntMask),
Port: IPPort(tcpAddr.Port),
}
return ipv6Addr, nil
}
// Parse as a naked IPv6 address. Trim square brackets if present.
if len(ipv6Str) > 2 && ipv6Str[0] == '[' && ipv6Str[len(ipv6Str)-1] == ']' {
ipv6Str = ipv6Str[1 : len(ipv6Str)-1]
}
ip := net.ParseIP(ipv6Str)
if ip != nil {
ipv6 := ip.To16()
if ipv6 == nil {
return IPv6Addr{}, fmt.Errorf("Unable to string convert %+q to a 16byte IPv6 address", ipv6Str)
}
ipv6BigIntAddr := new(big.Int)
ipv6BigIntAddr.SetBytes(ipv6)
ipv6BigIntMask := new(big.Int)
ipv6BigIntMask.Set(ipv6HostMask)
return IPv6Addr{
Address: IPv6Address(ipv6BigIntAddr),
Mask: IPv6Mask(ipv6BigIntMask),
}, nil
}
// Parse as an IPv6 CIDR
ipAddr, network, err := net.ParseCIDR(ipv6Str)
if err == nil {
ipv6 := ipAddr.To16()
if ipv6 == nil {
return IPv6Addr{}, fmt.Errorf("Unable to convert %+q to a 16byte IPv6 address", ipv6Str)
}
ipv6BigIntAddr := new(big.Int)
ipv6BigIntAddr.SetBytes(ipv6)
ipv6BigIntMask := new(big.Int)
ipv6BigIntMask.SetBytes(network.Mask)
ipv6Addr := IPv6Addr{
Address: IPv6Address(ipv6BigIntAddr),
Mask: IPv6Mask(ipv6BigIntMask),
}
return ipv6Addr, nil
}
return IPv6Addr{}, fmt.Errorf("Unable to parse %+q to an IPv6 address: %v", ipv6Str, err)
}
// AddressBinString returns a string with the IPv6Addr's Address represented
// as a sequence of '0' and '1' characters. This method is useful for
// debugging or by operators who want to inspect an address.
func (ipv6 IPv6Addr) AddressBinString() string {
bi := big.Int(*ipv6.Address)
return fmt.Sprintf("%0128s", bi.Text(2))
}
// AddressHexString returns a string with the IPv6Addr address represented as
// a sequence of hex characters. This method is useful for debugging or by
// operators who want to inspect an address.
func (ipv6 IPv6Addr) AddressHexString() string {
bi := big.Int(*ipv6.Address)
return fmt.Sprintf("%032s", bi.Text(16))
}
// CmpAddress follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its address is lower than arg
// - 0 if the SockAddr arg equal to the receiving IPv6Addr or the argument is of a
// different type.
// - 1 If the argument should sort first.
func (ipv6 IPv6Addr) CmpAddress(sa SockAddr) int {
ipv6b, ok := sa.(IPv6Addr)
if !ok {
return sortDeferDecision
}
ipv6aBigInt := new(big.Int)
ipv6aBigInt.Set(ipv6.Address)
ipv6bBigInt := new(big.Int)
ipv6bBigInt.Set(ipv6b.Address)
return ipv6aBigInt.Cmp(ipv6bBigInt)
}
// CmpPort follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its port is lower than arg
// - 0 if the SockAddr arg's port number is equal to the receiving IPv6Addr,
// regardless of type.
// - 1 If the argument should sort first.
func (ipv6 IPv6Addr) CmpPort(sa SockAddr) int {
var saPort IPPort
switch v := sa.(type) {
case IPv4Addr:
saPort = v.Port
case IPv6Addr:
saPort = v.Port
default:
return sortDeferDecision
}
switch {
case ipv6.Port == saPort:
return sortDeferDecision
case ipv6.Port < saPort:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// CmpRFC follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because it belongs to the RFC and its
// arg does not
// - 0 if the receiver and arg both belong to the same RFC or neither do.
// - 1 If the arg belongs to the RFC but receiver does not.
func (ipv6 IPv6Addr) CmpRFC(rfcNum uint, sa SockAddr) int {
recvInRFC := IsRFC(rfcNum, ipv6)
ipv6b, ok := sa.(IPv6Addr)
if !ok {
// If the receiver is part of the desired RFC and the SockAddr
// argument is not, sort receiver before the non-IPv6 SockAddr.
// Conversely, if the receiver is not part of the RFC, punt on
// sorting and leave it for the next sorter.
if recvInRFC {
return sortReceiverBeforeArg
} else {
return sortDeferDecision
}
}
argInRFC := IsRFC(rfcNum, ipv6b)
switch {
case (recvInRFC && argInRFC), (!recvInRFC && !argInRFC):
// If a and b both belong to the RFC, or neither belong to
// rfcNum, defer sorting to the next sorter.
return sortDeferDecision
case recvInRFC && !argInRFC:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// Contains returns true if the SockAddr is contained within the receiver.
func (ipv6 IPv6Addr) Contains(sa SockAddr) bool {
ipv6b, ok := sa.(IPv6Addr)
if !ok {
return false
}
return ipv6.ContainsNetwork(ipv6b)
}
// ContainsAddress returns true if the IPv6Address is contained within the
// receiver.
func (ipv6 IPv6Addr) ContainsAddress(x IPv6Address) bool {
xAddr := IPv6Addr{
Address: x,
Mask: ipv6HostMask,
}
{
xIPv6 := xAddr.FirstUsable().(IPv6Addr)
yIPv6 := ipv6.FirstUsable().(IPv6Addr)
if xIPv6.CmpAddress(yIPv6) >= 1 {
return false
}
}
{
xIPv6 := xAddr.LastUsable().(IPv6Addr)
yIPv6 := ipv6.LastUsable().(IPv6Addr)
if xIPv6.CmpAddress(yIPv6) <= -1 {
return false
}
}
return true
}
// ContainsNetwork returns true if the network from IPv6Addr is contained within
// the receiver.
func (x IPv6Addr) ContainsNetwork(y IPv6Addr) bool {
{
xIPv6 := x.FirstUsable().(IPv6Addr)
yIPv6 := y.FirstUsable().(IPv6Addr)
if ret := xIPv6.CmpAddress(yIPv6); ret >= 1 {
return false
}
}
{
xIPv6 := x.LastUsable().(IPv6Addr)
yIPv6 := y.LastUsable().(IPv6Addr)
if ret := xIPv6.CmpAddress(yIPv6); ret <= -1 {
return false
}
}
return true
}
// DialPacketArgs returns the arguments required to be passed to
// net.DialUDP(). If the Mask of ipv6 is not a /128 or the Port is 0,
// DialPacketArgs() will fail. See Host() to create an IPv6Addr with its
// mask set to /128.
func (ipv6 IPv6Addr) DialPacketArgs() (network, dialArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 || ipv6.Port == 0 {
return "udp6", ""
}
return "udp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// DialStreamArgs returns the arguments required to be passed to
// net.DialTCP(). If the Mask of ipv6 is not a /128 or the Port is 0,
// DialStreamArgs() will fail. See Host() to create an IPv6Addr with its
// mask set to /128.
func (ipv6 IPv6Addr) DialStreamArgs() (network, dialArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 || ipv6.Port == 0 {
return "tcp6", ""
}
return "tcp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// Equal returns true if a SockAddr is equal to the receiving IPv4Addr.
func (ipv6a IPv6Addr) Equal(sa SockAddr) bool {
ipv6b, ok := sa.(IPv6Addr)
if !ok {
return false
}
if ipv6a.NetIP().String() != ipv6b.NetIP().String() {
return false
}
if ipv6a.NetIPNet().String() != ipv6b.NetIPNet().String() {
return false
}
if ipv6a.Port != ipv6b.Port {
return false
}
return true
}
// FirstUsable returns an IPv6Addr set to the first address following the
// network prefix. The first usable address in a network is normally the
// gateway and should not be used except by devices forwarding packets
// between two administratively distinct networks (i.e. a router). This
// function does not discriminate against first usable vs "first address that
// should be used." For example, FirstUsable() on "2001:0db8::0003/64" would
// return "2001:0db8::00011".
func (ipv6 IPv6Addr) FirstUsable() IPAddr {
return IPv6Addr{
Address: IPv6Address(ipv6.NetworkAddress()),
Mask: ipv6HostMask,
}
}
// Host returns a copy of ipv6 with its mask set to /128 so that it can be
// used by DialPacketArgs(), DialStreamArgs(), ListenPacketArgs(), or
// ListenStreamArgs().
func (ipv6 IPv6Addr) Host() IPAddr {
// Nothing should listen on a broadcast address.
return IPv6Addr{
Address: ipv6.Address,
Mask: ipv6HostMask,
Port: ipv6.Port,
}
}
// IPPort returns the Port number attached to the IPv6Addr
func (ipv6 IPv6Addr) IPPort() IPPort {
return ipv6.Port
}
// LastUsable returns the last address in a given network.
func (ipv6 IPv6Addr) LastUsable() IPAddr {
addr := new(big.Int)
addr.Set(ipv6.Address)
mask := new(big.Int)
mask.Set(ipv6.Mask)
negMask := new(big.Int)
negMask.Xor(ipv6HostMask, mask)
lastAddr := new(big.Int)
lastAddr.And(addr, mask)
lastAddr.Or(lastAddr, negMask)
return IPv6Addr{
Address: IPv6Address(lastAddr),
Mask: ipv6HostMask,
}
}
// ListenPacketArgs returns the arguments required to be passed to
// net.ListenUDP(). If the Mask of ipv6 is not a /128, ListenPacketArgs()
// will fail. See Host() to create an IPv6Addr with its mask set to /128.
func (ipv6 IPv6Addr) ListenPacketArgs() (network, listenArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 {
return "udp6", ""
}
return "udp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// ListenStreamArgs returns the arguments required to be passed to
// net.ListenTCP(). If the Mask of ipv6 is not a /128, ListenStreamArgs()
// will fail. See Host() to create an IPv6Addr with its mask set to /128.
func (ipv6 IPv6Addr) ListenStreamArgs() (network, listenArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 {
return "tcp6", ""
}
return "tcp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// Maskbits returns the number of network mask bits in a given IPv6Addr. For
// example, the Maskbits() of "2001:0db8::0003/64" would return 64.
func (ipv6 IPv6Addr) Maskbits() int {
maskOnes, _ := ipv6.NetIPNet().Mask.Size()
return maskOnes
}
// MustIPv6Addr is a helper method that must return an IPv6Addr or panic on
// invalid input.
func MustIPv6Addr(addr string) IPv6Addr {
ipv6, err := NewIPv6Addr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPv6Addr from %+q: %v", addr, err))
}
return ipv6
}
// NetIP returns the address as a net.IP.
func (ipv6 IPv6Addr) NetIP() *net.IP {
return bigIntToNetIPv6(ipv6.Address)
}
// NetIPMask create a new net.IPMask from the IPv6Addr.
func (ipv6 IPv6Addr) NetIPMask() *net.IPMask {
ipv6Mask := make(net.IPMask, IPv6len)
m := big.Int(*ipv6.Mask)
copy(ipv6Mask, m.Bytes())
return &ipv6Mask
}
// Network returns a pointer to the net.IPNet within IPv4Addr receiver.
func (ipv6 IPv6Addr) NetIPNet() *net.IPNet {
ipv6net := &net.IPNet{}
ipv6net.IP = make(net.IP, IPv6len)
copy(ipv6net.IP, *ipv6.NetIP())
ipv6net.Mask = *ipv6.NetIPMask()
return ipv6net
}
// Network returns the network prefix or network address for a given network.
func (ipv6 IPv6Addr) Network() IPAddr {
return IPv6Addr{
Address: IPv6Address(ipv6.NetworkAddress()),
Mask: ipv6.Mask,
}
}
// NetworkAddress returns an IPv6Network of the IPv6Addr's network address.
func (ipv6 IPv6Addr) NetworkAddress() IPv6Network {
addr := new(big.Int)
addr.SetBytes((*ipv6.Address).Bytes())
mask := new(big.Int)
mask.SetBytes(*ipv6.NetIPMask())
netAddr := new(big.Int)
netAddr.And(addr, mask)
return IPv6Network(netAddr)
}
// Octets returns a slice of the 16 octets in an IPv6Addr's Address. The
// order of the bytes is big endian.
func (ipv6 IPv6Addr) Octets() []int {
x := make([]int, IPv6len)
for i, b := range *bigIntToNetIPv6(ipv6.Address) {
x[i] = int(b)
}
return x
}
// String returns a string representation of the IPv6Addr
func (ipv6 IPv6Addr) String() string {
if ipv6.Port != 0 {
return fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
if ipv6.Maskbits() == 128 {
return ipv6.NetIP().String()
}
return fmt.Sprintf("%s/%d", ipv6.NetIP().String(), ipv6.Maskbits())
}
// Type is used as a type switch and returns TypeIPv6
func (IPv6Addr) Type() SockAddrType {
return TypeIPv6
}
// IPv6Attrs returns a list of attributes supported by the IPv6Addr type
func IPv6Attrs() []AttrName {
return ipv6AddrAttrs
}
// IPv6AddrAttr returns a string representation of an attribute for the given
// IPv6Addr.
func IPv6AddrAttr(ipv6 IPv6Addr, selector AttrName) string {
fn, found := ipv6AddrAttrMap[selector]
if !found {
return ""
}
return fn(ipv6)
}
// ipv6AddrInit is called once at init()
func ipv6AddrInit() {
// Sorted for human readability
ipv6AddrAttrs = []AttrName{
"size", // Same position as in IPv6 for output consistency
"uint128",
}
ipv6AddrAttrMap = map[AttrName]func(ipv6 IPv6Addr) string{
"size": func(ipv6 IPv6Addr) string {
netSize := big.NewInt(1)
netSize = netSize.Lsh(netSize, uint(IPv6len*8-ipv6.Maskbits()))
return netSize.Text(10)
},
"uint128": func(ipv6 IPv6Addr) string {
b := big.Int(*ipv6.Address)
return b.Text(10)
},
}
}
// bigIntToNetIPv6 is a helper function that correctly returns a net.IP with the
// correctly padded values.
func bigIntToNetIPv6(bi *big.Int) *net.IP {
x := make(net.IP, IPv6len)
ipv6Bytes := bi.Bytes()
// It's possibe for ipv6Bytes to be less than IPv6len bytes in size. If
// they are different sizes we to pad the size of response.
if len(ipv6Bytes) < IPv6len {
buf := new(bytes.Buffer)
buf.Grow(IPv6len)
for i := len(ipv6Bytes); i < IPv6len; i++ {
if err := binary.Write(buf, binary.BigEndian, byte(0)); err != nil {
panic(fmt.Sprintf("Unable to pad byte %d of input %v: %v", i, bi, err))
}
}
for _, b := range ipv6Bytes {
if err := binary.Write(buf, binary.BigEndian, b); err != nil {
panic(fmt.Sprintf("Unable to preserve endianness of input %v: %v", bi, err))
}
}
ipv6Bytes = buf.Bytes()
}
i := copy(x, ipv6Bytes)
if i != IPv6len {
panic("IPv6 wrong size")
}
return &x
}
+948
View File
@@ -0,0 +1,948 @@
package sockaddr
// ForwardingBlacklist is a faux RFC that includes a list of non-forwardable IP
// blocks.
const ForwardingBlacklist = 4294967295
const ForwardingBlacklistRFC = "4294967295"
// IsRFC tests to see if an SockAddr matches the specified RFC
func IsRFC(rfcNum uint, sa SockAddr) bool {
rfcNetMap := KnownRFCs()
rfcNets, ok := rfcNetMap[rfcNum]
if !ok {
return false
}
var contained bool
for _, rfcNet := range rfcNets {
if rfcNet.Contains(sa) {
contained = true
break
}
}
return contained
}
// KnownRFCs returns an initial set of known RFCs.
//
// NOTE (sean@): As this list evolves over time, please submit patches to keep
// this list current. If something isn't right, inquire, as it may just be a
// bug on my part. Some of the inclusions were based on my judgement as to what
// would be a useful value (e.g. RFC3330).
//
// Useful resources:
//
// * https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
// * https://www.iana.org/assignments/ipv6-unicast-address-assignments/ipv6-unicast-address-assignments.xhtml
// * https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
func KnownRFCs() map[uint]SockAddrs {
// NOTE(sean@): Multiple SockAddrs per RFC lend themselves well to a
// RADIX tree, but `ENOTIME`. Patches welcome.
return map[uint]SockAddrs{
919: {
// [RFC919] Broadcasting Internet Datagrams
MustIPv4Addr("255.255.255.255/32"), // [RFC1122], §7 Broadcast IP Addressing - Proposed Standards
},
1122: {
// [RFC1122] Requirements for Internet Hosts -- Communication Layers
MustIPv4Addr("0.0.0.0/8"), // [RFC1122], §3.2.1.3
MustIPv4Addr("127.0.0.0/8"), // [RFC1122], §3.2.1.3
},
1112: {
// [RFC1112] Host Extensions for IP Multicasting
MustIPv4Addr("224.0.0.0/4"), // [RFC1112], §4 Host Group Addresses
},
1918: {
// [RFC1918] Address Allocation for Private Internets
MustIPv4Addr("10.0.0.0/8"),
MustIPv4Addr("172.16.0.0/12"),
MustIPv4Addr("192.168.0.0/16"),
},
2544: {
// [RFC2544] Benchmarking Methodology for Network
// Interconnect Devices
MustIPv4Addr("198.18.0.0/15"),
},
2765: {
// [RFC2765] Stateless IP/ICMP Translation Algorithm
// (SIIT) (obsoleted by RFCs 6145, which itself was
// later obsoleted by 7915).
// [RFC2765], §2.1 Addresses
MustIPv6Addr("0:0:0:0:0:ffff:0:0/96"),
},
2928: {
// [RFC2928] Initial IPv6 Sub-TLA ID Assignments
MustIPv6Addr("2001::/16"), // Superblock
//MustIPv6Addr("2001:0000::/23"), // IANA
//MustIPv6Addr("2001:0200::/23"), // APNIC
//MustIPv6Addr("2001:0400::/23"), // ARIN
//MustIPv6Addr("2001:0600::/23"), // RIPE NCC
//MustIPv6Addr("2001:0800::/23"), // (future assignment)
// ...
//MustIPv6Addr("2001:FE00::/23"), // (future assignment)
},
3056: { // 6to4 address
// [RFC3056] Connection of IPv6 Domains via IPv4 Clouds
// [RFC3056], §2 IPv6 Prefix Allocation
MustIPv6Addr("2002::/16"),
},
3068: {
// [RFC3068] An Anycast Prefix for 6to4 Relay Routers
// (obsolete by RFC7526)
// [RFC3068], § 6to4 Relay anycast address
MustIPv4Addr("192.88.99.0/24"),
// [RFC3068], §2.5 6to4 IPv6 relay anycast address
//
// NOTE: /120 == 128-(32-24)
MustIPv6Addr("2002:c058:6301::/120"),
},
3171: {
// [RFC3171] IANA Guidelines for IPv4 Multicast Address Assignments
MustIPv4Addr("224.0.0.0/4"),
},
3330: {
// [RFC3330] Special-Use IPv4 Addresses
// Addresses in this block refer to source hosts on
// "this" network. Address 0.0.0.0/32 may be used as a
// source address for this host on this network; other
// addresses within 0.0.0.0/8 may be used to refer to
// specified hosts on this network [RFC1700, page 4].
MustIPv4Addr("0.0.0.0/8"),
// 10.0.0.0/8 - This block is set aside for use in
// private networks. Its intended use is documented in
// [RFC1918]. Addresses within this block should not
// appear on the public Internet.
MustIPv4Addr("10.0.0.0/8"),
// 14.0.0.0/8 - This block is set aside for assignments
// to the international system of Public Data Networks
// [RFC1700, page 181]. The registry of assignments
// within this block can be accessed from the "Public
// Data Network Numbers" link on the web page at
// http://www.iana.org/numbers.html. Addresses within
// this block are assigned to users and should be
// treated as such.
// 24.0.0.0/8 - This block was allocated in early 1996
// for use in provisioning IP service over cable
// television systems. Although the IANA initially was
// involved in making assignments to cable operators,
// this responsibility was transferred to American
// Registry for Internet Numbers (ARIN) in May 2001.
// Addresses within this block are assigned in the
// normal manner and should be treated as such.
// 39.0.0.0/8 - This block was used in the "Class A
// Subnet Experiment" that commenced in May 1995, as
// documented in [RFC1797]. The experiment has been
// completed and this block has been returned to the
// pool of addresses reserved for future allocation or
// assignment. This block therefore no longer has a
// special use and is subject to allocation to a
// Regional Internet Registry for assignment in the
// normal manner.
// 127.0.0.0/8 - This block is assigned for use as the Internet host
// loopback address. A datagram sent by a higher level protocol to an
// address anywhere within this block should loop back inside the host.
// This is ordinarily implemented using only 127.0.0.1/32 for loopback,
// but no addresses within this block should ever appear on any network
// anywhere [RFC1700, page 5].
MustIPv4Addr("127.0.0.0/8"),
// 128.0.0.0/16 - This block, corresponding to the
// numerically lowest of the former Class B addresses,
// was initially and is still reserved by the IANA.
// Given the present classless nature of the IP address
// space, the basis for the reservation no longer
// applies and addresses in this block are subject to
// future allocation to a Regional Internet Registry for
// assignment in the normal manner.
// 169.254.0.0/16 - This is the "link local" block. It
// is allocated for communication between hosts on a
// single link. Hosts obtain these addresses by
// auto-configuration, such as when a DHCP server may
// not be found.
MustIPv4Addr("169.254.0.0/16"),
// 172.16.0.0/12 - This block is set aside for use in
// private networks. Its intended use is documented in
// [RFC1918]. Addresses within this block should not
// appear on the public Internet.
MustIPv4Addr("172.16.0.0/12"),
// 191.255.0.0/16 - This block, corresponding to the numerically highest
// to the former Class B addresses, was initially and is still reserved
// by the IANA. Given the present classless nature of the IP address
// space, the basis for the reservation no longer applies and addresses
// in this block are subject to future allocation to a Regional Internet
// Registry for assignment in the normal manner.
// 192.0.0.0/24 - This block, corresponding to the
// numerically lowest of the former Class C addresses,
// was initially and is still reserved by the IANA.
// Given the present classless nature of the IP address
// space, the basis for the reservation no longer
// applies and addresses in this block are subject to
// future allocation to a Regional Internet Registry for
// assignment in the normal manner.
// 192.0.2.0/24 - This block is assigned as "TEST-NET" for use in
// documentation and example code. It is often used in conjunction with
// domain names example.com or example.net in vendor and protocol
// documentation. Addresses within this block should not appear on the
// public Internet.
MustIPv4Addr("192.0.2.0/24"),
// 192.88.99.0/24 - This block is allocated for use as 6to4 relay
// anycast addresses, according to [RFC3068].
MustIPv4Addr("192.88.99.0/24"),
// 192.168.0.0/16 - This block is set aside for use in private networks.
// Its intended use is documented in [RFC1918]. Addresses within this
// block should not appear on the public Internet.
MustIPv4Addr("192.168.0.0/16"),
// 198.18.0.0/15 - This block has been allocated for use
// in benchmark tests of network interconnect devices.
// Its use is documented in [RFC2544].
MustIPv4Addr("198.18.0.0/15"),
// 223.255.255.0/24 - This block, corresponding to the
// numerically highest of the former Class C addresses,
// was initially and is still reserved by the IANA.
// Given the present classless nature of the IP address
// space, the basis for the reservation no longer
// applies and addresses in this block are subject to
// future allocation to a Regional Internet Registry for
// assignment in the normal manner.
// 224.0.0.0/4 - This block, formerly known as the Class
// D address space, is allocated for use in IPv4
// multicast address assignments. The IANA guidelines
// for assignments from this space are described in
// [RFC3171].
MustIPv4Addr("224.0.0.0/4"),
// 240.0.0.0/4 - This block, formerly known as the Class E address
// space, is reserved. The "limited broadcast" destination address
// 255.255.255.255 should never be forwarded outside the (sub-)net of
// the source. The remainder of this space is reserved
// for future use. [RFC1700, page 4]
MustIPv4Addr("240.0.0.0/4"),
},
3849: {
// [RFC3849] IPv6 Address Prefix Reserved for Documentation
MustIPv6Addr("2001:db8::/32"), // [RFC3849], §4 IANA Considerations
},
3927: {
// [RFC3927] Dynamic Configuration of IPv4 Link-Local Addresses
MustIPv4Addr("169.254.0.0/16"), // [RFC3927], §2.1 Link-Local Address Selection
},
4038: {
// [RFC4038] Application Aspects of IPv6 Transition
// [RFC4038], §4.2. IPv6 Applications in a Dual-Stack Node
MustIPv6Addr("0:0:0:0:0:ffff::/96"),
},
4193: {
// [RFC4193] Unique Local IPv6 Unicast Addresses
MustIPv6Addr("fc00::/7"),
},
4291: {
// [RFC4291] IP Version 6 Addressing Architecture
// [RFC4291], §2.5.2 The Unspecified Address
MustIPv6Addr("::/128"),
// [RFC4291], §2.5.3 The Loopback Address
MustIPv6Addr("::1/128"),
// [RFC4291], §2.5.5.1. IPv4-Compatible IPv6 Address
MustIPv6Addr("::/96"),
// [RFC4291], §2.5.5.2. IPv4-Mapped IPv6 Address
MustIPv6Addr("::ffff:0:0/96"),
// [RFC4291], §2.5.6 Link-Local IPv6 Unicast Addresses
MustIPv6Addr("fe80::/10"),
// [RFC4291], §2.5.7 Site-Local IPv6 Unicast Addresses
// (depreciated)
MustIPv6Addr("fec0::/10"),
// [RFC4291], §2.7 Multicast Addresses
MustIPv6Addr("ff00::/8"),
// IPv6 Multicast Information.
//
// In the following "table" below, `ff0x` is replaced
// with the following values depending on the scope of
// the query:
//
// IPv6 Multicast Scopes:
// * ff00/9 // reserved
// * ff01/9 // interface-local
// * ff02/9 // link-local
// * ff03/9 // realm-local
// * ff04/9 // admin-local
// * ff05/9 // site-local
// * ff08/9 // organization-local
// * ff0e/9 // global
// * ff0f/9 // reserved
//
// IPv6 Multicast Addresses:
// * ff0x::2 // All routers
// * ff02::5 // OSPFIGP
// * ff02::6 // OSPFIGP Designated Routers
// * ff02::9 // RIP Routers
// * ff02::a // EIGRP Routers
// * ff02::d // All PIM Routers
// * ff02::1a // All RPL Routers
// * ff0x::fb // mDNSv6
// * ff0x::101 // All Network Time Protocol (NTP) servers
// * ff02::1:1 // Link Name
// * ff02::1:2 // All-dhcp-agents
// * ff02::1:3 // Link-local Multicast Name Resolution
// * ff05::1:3 // All-dhcp-servers
// * ff02::1:ff00:0/104 // Solicited-node multicast address.
// * ff02::2:ff00:0/104 // Node Information Queries
},
4380: {
// [RFC4380] Teredo: Tunneling IPv6 over UDP through
// Network Address Translations (NATs)
// [RFC4380], §2.6 Global Teredo IPv6 Service Prefix
MustIPv6Addr("2001:0000::/32"),
},
4773: {
// [RFC4773] Administration of the IANA Special Purpose IPv6 Address Block
MustIPv6Addr("2001:0000::/23"), // IANA
},
4843: {
// [RFC4843] An IPv6 Prefix for Overlay Routable Cryptographic Hash Identifiers (ORCHID)
MustIPv6Addr("2001:10::/28"), // [RFC4843], §7 IANA Considerations
},
5180: {
// [RFC5180] IPv6 Benchmarking Methodology for Network Interconnect Devices
MustIPv6Addr("2001:0200::/48"), // [RFC5180], §8 IANA Considerations
},
5735: {
// [RFC5735] Special Use IPv4 Addresses
MustIPv4Addr("192.0.2.0/24"), // TEST-NET-1
MustIPv4Addr("198.51.100.0/24"), // TEST-NET-2
MustIPv4Addr("203.0.113.0/24"), // TEST-NET-3
MustIPv4Addr("198.18.0.0/15"), // Benchmarks
},
5737: {
// [RFC5737] IPv4 Address Blocks Reserved for Documentation
MustIPv4Addr("192.0.2.0/24"), // TEST-NET-1
MustIPv4Addr("198.51.100.0/24"), // TEST-NET-2
MustIPv4Addr("203.0.113.0/24"), // TEST-NET-3
},
6052: {
// [RFC6052] IPv6 Addressing of IPv4/IPv6 Translators
MustIPv6Addr("64:ff9b::/96"), // [RFC6052], §2.1. Well-Known Prefix
},
6333: {
// [RFC6333] Dual-Stack Lite Broadband Deployments Following IPv4 Exhaustion
MustIPv4Addr("192.0.0.0/29"), // [RFC6333], §5.7 Well-Known IPv4 Address
},
6598: {
// [RFC6598] IANA-Reserved IPv4 Prefix for Shared Address Space
MustIPv4Addr("100.64.0.0/10"),
},
6666: {
// [RFC6666] A Discard Prefix for IPv6
MustIPv6Addr("0100::/64"),
},
6890: {
// [RFC6890] Special-Purpose IP Address Registries
// From "RFC6890 §2.2.1 Information Requirements":
/*
The IPv4 and IPv6 Special-Purpose Address Registries maintain the
following information regarding each entry:
o Address Block - A block of IPv4 or IPv6 addresses that has been
registered for a special purpose.
o Name - A descriptive name for the special-purpose address block.
o RFC - The RFC through which the special-purpose address block was
requested.
o Allocation Date - The date upon which the special-purpose address
block was allocated.
o Termination Date - The date upon which the allocation is to be
terminated. This field is applicable for limited-use allocations
only.
o Source - A boolean value indicating whether an address from the
allocated special-purpose address block is valid when used as the
source address of an IP datagram that transits two devices.
o Destination - A boolean value indicating whether an address from
the allocated special-purpose address block is valid when used as
the destination address of an IP datagram that transits two
devices.
o Forwardable - A boolean value indicating whether a router may
forward an IP datagram whose destination address is drawn from the
allocated special-purpose address block between external
interfaces.
o Global - A boolean value indicating whether an IP datagram whose
destination address is drawn from the allocated special-purpose
address block is forwardable beyond a specified administrative
domain.
o Reserved-by-Protocol - A boolean value indicating whether the
special-purpose address block is reserved by IP, itself. This
value is "TRUE" if the RFC that created the special-purpose
address block requires all compliant IP implementations to behave
in a special way when processing packets either to or from
addresses contained by the address block.
If the value of "Destination" is FALSE, the values of "Forwardable"
and "Global" must also be false.
*/
/*+----------------------+----------------------------+
* | Attribute | Value |
* +----------------------+----------------------------+
* | Address Block | 0.0.0.0/8 |
* | Name | "This host on this network"|
* | RFC | [RFC1122], Section 3.2.1.3 |
* | Allocation Date | September 1981 |
* | Termination Date | N/A |
* | Source | True |
* | Destination | False |
* | Forwardable | False |
* | Global | False |
* | Reserved-by-Protocol | True |
* +----------------------+----------------------------+*/
MustIPv4Addr("0.0.0.0/8"),
/*+----------------------+---------------+
* | Attribute | Value |
* +----------------------+---------------+
* | Address Block | 10.0.0.0/8 |
* | Name | Private-Use |
* | RFC | [RFC1918] |
* | Allocation Date | February 1996 |
* | Termination Date | N/A |
* | Source | True |
* | Destination | True |
* | Forwardable | True |
* | Global | False |
* | Reserved-by-Protocol | False |
* +----------------------+---------------+ */
MustIPv4Addr("10.0.0.0/8"),
/*+----------------------+----------------------+
| Attribute | Value |
+----------------------+----------------------+
| Address Block | 100.64.0.0/10 |
| Name | Shared Address Space |
| RFC | [RFC6598] |
| Allocation Date | April 2012 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------+*/
MustIPv4Addr("100.64.0.0/10"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 127.0.0.0/8 |
| Name | Loopback |
| RFC | [RFC1122], Section 3.2.1.3 |
| Allocation Date | September 1981 |
| Termination Date | N/A |
| Source | False [1] |
| Destination | False [1] |
| Forwardable | False [1] |
| Global | False [1] |
| Reserved-by-Protocol | True |
+----------------------+----------------------------+*/
// [1] Several protocols have been granted exceptions to
// this rule. For examples, see [RFC4379] and
// [RFC5884].
MustIPv4Addr("127.0.0.0/8"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 169.254.0.0/16 |
| Name | Link Local |
| RFC | [RFC3927] |
| Allocation Date | May 2005 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+----------------+*/
MustIPv4Addr("169.254.0.0/16"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 172.16.0.0/12 |
| Name | Private-Use |
| RFC | [RFC1918] |
| Allocation Date | February 1996 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
MustIPv4Addr("172.16.0.0/12"),
/*+----------------------+---------------------------------+
| Attribute | Value |
+----------------------+---------------------------------+
| Address Block | 192.0.0.0/24 [2] |
| Name | IETF Protocol Assignments |
| RFC | Section 2.1 of this document |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------------------------+*/
// [2] Not usable unless by virtue of a more specific
// reservation.
MustIPv4Addr("192.0.0.0/24"),
/*+----------------------+--------------------------------+
| Attribute | Value |
+----------------------+--------------------------------+
| Address Block | 192.0.0.0/29 |
| Name | IPv4 Service Continuity Prefix |
| RFC | [RFC6333], [RFC7335] |
| Allocation Date | June 2011 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+--------------------------------+*/
MustIPv4Addr("192.0.0.0/29"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 192.0.2.0/24 |
| Name | Documentation (TEST-NET-1) |
| RFC | [RFC5737] |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv4Addr("192.0.2.0/24"),
/*+----------------------+--------------------+
| Attribute | Value |
+----------------------+--------------------+
| Address Block | 192.88.99.0/24 |
| Name | 6to4 Relay Anycast |
| RFC | [RFC3068] |
| Allocation Date | June 2001 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | True |
| Reserved-by-Protocol | False |
+----------------------+--------------------+*/
MustIPv4Addr("192.88.99.0/24"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 192.168.0.0/16 |
| Name | Private-Use |
| RFC | [RFC1918] |
| Allocation Date | February 1996 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------+*/
MustIPv4Addr("192.168.0.0/16"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 198.18.0.0/15 |
| Name | Benchmarking |
| RFC | [RFC2544] |
| Allocation Date | March 1999 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
MustIPv4Addr("198.18.0.0/15"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 198.51.100.0/24 |
| Name | Documentation (TEST-NET-2) |
| RFC | [RFC5737] |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv4Addr("198.51.100.0/24"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 203.0.113.0/24 |
| Name | Documentation (TEST-NET-3) |
| RFC | [RFC5737] |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv4Addr("203.0.113.0/24"),
/*+----------------------+----------------------+
| Attribute | Value |
+----------------------+----------------------+
| Address Block | 240.0.0.0/4 |
| Name | Reserved |
| RFC | [RFC1112], Section 4 |
| Allocation Date | August 1989 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+----------------------+*/
MustIPv4Addr("240.0.0.0/4"),
/*+----------------------+----------------------+
| Attribute | Value |
+----------------------+----------------------+
| Address Block | 255.255.255.255/32 |
| Name | Limited Broadcast |
| RFC | [RFC0919], Section 7 |
| Allocation Date | October 1984 |
| Termination Date | N/A |
| Source | False |
| Destination | True |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------+*/
MustIPv4Addr("255.255.255.255/32"),
/*+----------------------+------------------+
| Attribute | Value |
+----------------------+------------------+
| Address Block | ::1/128 |
| Name | Loopback Address |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+------------------+*/
MustIPv6Addr("::1/128"),
/*+----------------------+---------------------+
| Attribute | Value |
+----------------------+---------------------+
| Address Block | ::/128 |
| Name | Unspecified Address |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | True |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+---------------------+*/
MustIPv6Addr("::/128"),
/*+----------------------+---------------------+
| Attribute | Value |
+----------------------+---------------------+
| Address Block | 64:ff9b::/96 |
| Name | IPv4-IPv6 Translat. |
| RFC | [RFC6052] |
| Allocation Date | October 2010 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | True |
| Reserved-by-Protocol | False |
+----------------------+---------------------+*/
MustIPv6Addr("64:ff9b::/96"),
/*+----------------------+---------------------+
| Attribute | Value |
+----------------------+---------------------+
| Address Block | ::ffff:0:0/96 |
| Name | IPv4-mapped Address |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+---------------------+*/
MustIPv6Addr("::ffff:0:0/96"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 100::/64 |
| Name | Discard-Only Address Block |
| RFC | [RFC6666] |
| Allocation Date | June 2012 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv6Addr("100::/64"),
/*+----------------------+---------------------------+
| Attribute | Value |
+----------------------+---------------------------+
| Address Block | 2001::/23 |
| Name | IETF Protocol Assignments |
| RFC | [RFC2928] |
| Allocation Date | September 2000 |
| Termination Date | N/A |
| Source | False[1] |
| Destination | False[1] |
| Forwardable | False[1] |
| Global | False[1] |
| Reserved-by-Protocol | False |
+----------------------+---------------------------+*/
// [1] Unless allowed by a more specific allocation.
MustIPv6Addr("2001::/16"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 2001::/32 |
| Name | TEREDO |
| RFC | [RFC4380] |
| Allocation Date | January 2006 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001::/16"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 2001:2::/48 |
| Name | Benchmarking |
| RFC | [RFC5180] |
| Allocation Date | April 2008 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001:2::/48"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 2001:db8::/32 |
| Name | Documentation |
| RFC | [RFC3849] |
| Allocation Date | July 2004 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001:db8::/32"),
/*+----------------------+--------------+
| Attribute | Value |
+----------------------+--------------+
| Address Block | 2001:10::/28 |
| Name | ORCHID |
| RFC | [RFC4843] |
| Allocation Date | March 2007 |
| Termination Date | March 2014 |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+--------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001:10::/28"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 2002::/16 [2] |
| Name | 6to4 |
| RFC | [RFC3056] |
| Allocation Date | February 2001 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | N/A [2] |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
// [2] See [RFC3056] for details.
MustIPv6Addr("2002::/16"),
/*+----------------------+--------------+
| Attribute | Value |
+----------------------+--------------+
| Address Block | fc00::/7 |
| Name | Unique-Local |
| RFC | [RFC4193] |
| Allocation Date | October 2005 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+--------------+*/
MustIPv6Addr("fc00::/7"),
/*+----------------------+-----------------------+
| Attribute | Value |
+----------------------+-----------------------+
| Address Block | fe80::/10 |
| Name | Linked-Scoped Unicast |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+-----------------------+*/
MustIPv6Addr("fe80::/10"),
},
7335: {
// [RFC7335] IPv4 Service Continuity Prefix
MustIPv4Addr("192.0.0.0/29"), // [RFC7335], §6 IANA Considerations
},
ForwardingBlacklist: { // Pseudo-RFC
// Blacklist of non-forwardable IP blocks taken from RFC6890
//
// TODO: the attributes for forwardable should be
// searcahble and embedded in the main list of RFCs
// above.
MustIPv4Addr("0.0.0.0/8"),
MustIPv4Addr("127.0.0.0/8"),
MustIPv4Addr("169.254.0.0/16"),
MustIPv4Addr("192.0.0.0/24"),
MustIPv4Addr("192.0.2.0/24"),
MustIPv4Addr("198.51.100.0/24"),
MustIPv4Addr("203.0.113.0/24"),
MustIPv4Addr("240.0.0.0/4"),
MustIPv4Addr("255.255.255.255/32"),
MustIPv6Addr("::1/128"),
MustIPv6Addr("::/128"),
MustIPv6Addr("::ffff:0:0/96"),
// There is no way of expressing a whitelist per RFC2928
// atm without creating a negative mask, which I don't
// want to do atm.
//MustIPv6Addr("2001::/23"),
MustIPv6Addr("2001:db8::/32"),
MustIPv6Addr("2001:10::/28"),
MustIPv6Addr("fe80::/10"),
},
}
}
// VisitAllRFCs iterates over all known RFCs and calls the visitor
func VisitAllRFCs(fn func(rfcNum uint, sockaddrs SockAddrs)) {
rfcNetMap := KnownRFCs()
// Blacklist of faux-RFCs. Don't show the world that we're abusing the
// RFC system in this library.
rfcBlacklist := map[uint]struct{}{
ForwardingBlacklist: {},
}
for rfcNum, sas := range rfcNetMap {
if _, found := rfcBlacklist[rfcNum]; !found {
fn(rfcNum, sas)
}
}
}
+19
View File
@@ -0,0 +1,19 @@
package sockaddr
// RouteInterface specifies an interface for obtaining memoized route table and
// network information from a given OS.
type RouteInterface interface {
// GetDefaultInterfaceName returns the name of the interface that has a
// default route or an error and an empty string if a problem was
// encountered.
GetDefaultInterfaceName() (string, error)
}
// VisitCommands visits each command used by the platform-specific RouteInfo
// implementation.
func (ri routeInfo) VisitCommands(fn func(name string, cmd []string)) {
for k, v := range ri.cmds {
cmds := append([]string(nil), v...)
fn(k, cmds)
}
}

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