diff --git a/crypto.go b/crypto.go index 5b1ca63..c541bbf 100644 --- a/crypto.go +++ b/crypto.go @@ -5,90 +5,91 @@ import ( "crypto/cipher" "crypto/rand" "encoding/json" - "fmt" "io" + + "github.com/pteich/errors" ) -func (s *Storage) encrypt(bytes []byte) ([]byte, error) { +func (cs *ConsulStorage) encrypt(bytes []byte) ([]byte, error) { // No key? No encrypt - if len(s.AESKey) == 0 { + if len(cs.AESKey) == 0 { return bytes, nil } - c, err := aes.NewCipher(s.AESKey) + c, err := aes.NewCipher(cs.AESKey) if err != nil { - return nil, fmt.Errorf("unable to create AES cipher: %w", err) + return nil, errors.Wrap(err, "unable to create AES cipher") } gcm, err := cipher.NewGCM(c) if err != nil { - return nil, fmt.Errorf("unable to create GCM cipher: %w", err) + return nil, errors.Wrap(err, "unable to create GCM cipher") } nonce := make([]byte, gcm.NonceSize()) _, err = io.ReadFull(rand.Reader, nonce) if err != nil { - return nil, fmt.Errorf("unable to generate nonce: %w", err) + return nil, errors.Wrap(err, "unable to generate nonce") } return gcm.Seal(nonce, nonce, bytes, nil), nil } -func (s *Storage) EncryptStorageData(data *StorageData) ([]byte, error) { +func (cs *ConsulStorage) EncryptStorageData(data *StorageData) ([]byte, error) { // JSON marshal, then encrypt if key is there bytes, err := json.Marshal(data) if err != nil { - return nil, fmt.Errorf("unable to marshal: %w", err) + return nil, errors.Wrap(err, "unable to marshal") } // Prefix with simple prefix and then encrypt - bytes = append([]byte(s.ValuePrefix), bytes...) - return s.encrypt(bytes) + bytes = append([]byte(cs.ValuePrefix), bytes...) + return cs.encrypt(bytes) } -func (s *Storage) decrypt(bytes []byte) ([]byte, error) { +func (cs *ConsulStorage) decrypt(bytes []byte) ([]byte, error) { // No key? No decrypt - if len(s.AESKey) == 0 { + if len(cs.AESKey) == 0 { return bytes, nil } if len(bytes) < aes.BlockSize { - return nil, fmt.Errorf("invalid contents") + return nil, errors.New("invalid contents") } - block, err := aes.NewCipher(s.AESKey) + block, err := aes.NewCipher(cs.AESKey) if err != nil { - return nil, fmt.Errorf("unable to create AES cipher: %w", err) + return nil, errors.Wrap(err, "unable to create AES cipher") } gcm, err := cipher.NewGCM(block) if err != nil { - return nil, fmt.Errorf("unable to create GCM cipher: %w", err) + return nil, errors.Wrap(err, "unable to create GCM cipher") } out, err := gcm.Open(nil, bytes[:gcm.NonceSize()], bytes[gcm.NonceSize():], nil) if err != nil { - return nil, fmt.Errorf("decryption failure: %w", err) + return nil, errors.Wrap(err, "decryption failure") } return out, nil } -func (s *Storage) DecryptStorageData(bytes []byte) (*StorageData, error) { +func (cs *ConsulStorage) DecryptStorageData(bytes []byte) (*StorageData, error) { // We have to decrypt if there is an AES key and then JSON unmarshal - bytes, err := s.decrypt(bytes) + bytes, err := cs.decrypt(bytes) if err != nil { - return nil, fmt.Errorf("unable to decrypt data: %w", err) + return nil, errors.Wrap(err, "unable to decrypt data") } // Simple sanity check of the beginning of the byte array just to check - if len(bytes) < len(s.ValuePrefix) || string(bytes[:len(s.ValuePrefix)]) != s.ValuePrefix { - return nil, fmt.Errorf("invalid data format") + if len(bytes) < len(cs.ValuePrefix) || string(bytes[:len(cs.ValuePrefix)]) != cs.ValuePrefix { + return nil, errors.New("invalid data format") } // Now just json unmarshal data := &StorageData{} - if err := json.Unmarshal(bytes[len(s.ValuePrefix):], data); err != nil { - return nil, fmt.Errorf("unable to unmarshal result: %w", err) + if err := json.Unmarshal(bytes[len(cs.ValuePrefix):], data); err != nil { + return nil, errors.Wrap(err, "unable to unmarshal result") } return data, nil } diff --git a/go.mod b/go.mod index 9b7edda..865826d 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,11 @@ require ( github.com/hashicorp/consul/api v1.7.0 github.com/hashicorp/go-hclog v0.14.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect - github.com/hashicorp/go.net v0.0.1 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.9.5 // indirect github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mitchellh/gox v0.4.0 // indirect - github.com/mitchellh/iochan v1.0.0 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/pteich/errors v1.0.1 github.com/stretchr/testify v1.5.1 go.uber.org/zap v1.15.0 golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect diff --git a/go.sum b/go.sum index 4e6cae7..91e3071 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,6 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= -github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.3.4 h1:Xqf+7f2Vhl9tsqDYmXhnXInUdcrtgpRNpIA15/uldSc= github.com/armon/go-metrics v0.3.4/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -285,12 +283,9 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= -github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY= -github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= github.com/hashicorp/consul/api v1.7.0 h1:tGs8Oep67r8CcA2Ycmb/8BLBcJ70St44mF2X10a/qPg= github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= -github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30= -github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw= github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= 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= @@ -303,8 +298,6 @@ github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQ github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8= -github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= @@ -322,7 +315,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -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= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -330,16 +322,9 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0= -github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM= github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= @@ -438,7 +423,6 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -449,14 +433,10 @@ github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzO github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -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 v0.0.0-20180220230111-00c29f56e238/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/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= -github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= @@ -548,6 +528,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx 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/pteich/errors v1.0.1 h1:ZQqdQcK079iH6YKb1/1hKSJ67+BuYBlSX3S07wNtEHE= +github.com/pteich/errors v1.0.1/go.mod h1:WgkErUbbxLTRULUtio3fSZfqAmeomF2958UjBIJlA4g= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -769,7 +751,6 @@ golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/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= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/module.go b/module.go index afe9f2d..9db9aae 100644 --- a/module.go +++ b/module.go @@ -10,10 +10,10 @@ import ( ) func init() { - caddy.RegisterModule(Storage{}) + caddy.RegisterModule(ConsulStorage{}) } -func (Storage) CaddyModule() caddy.ModuleInfo { +func (ConsulStorage) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "caddy.storage.consul", New: func() caddy.Module { @@ -23,28 +23,28 @@ func (Storage) CaddyModule() caddy.ModuleInfo { } // Provision is called by Caddy to prepare the module -func (s *Storage) Provision(ctx caddy.Context) error { - s.logger = ctx.Logger(s).Sugar() - s.logger.Infof("TLS storage is using Consul at %s", s.Address) +func (cs *ConsulStorage) Provision(ctx caddy.Context) error { + cs.logger = ctx.Logger(cs).Sugar() + cs.logger.Infof("TLS storage is using Consul at %cs", cs.Address) // override default values from ENV if aesKey := os.Getenv(EnvNameAESKey); aesKey != "" { - s.AESKey = []byte(aesKey) + cs.AESKey = []byte(aesKey) } if prefix := os.Getenv(EnvNamePrefix); prefix != "" { - s.Prefix = prefix + cs.Prefix = prefix } if valueprefix := os.Getenv(EnvValuePrefix); valueprefix != "" { - s.ValuePrefix = valueprefix + cs.ValuePrefix = valueprefix } - return s.createConsulClient() + return cs.createConsulClient() } -func (s *Storage) CertMagicStorage() (certmagic.Storage, error) { - return s, nil +func (cs *ConsulStorage) CertMagicStorage() (certmagic.Storage, error) { + return cs, nil } // UnmarshalCaddyfile parses plugin settings from Caddyfile @@ -58,7 +58,7 @@ func (s *Storage) CertMagicStorage() (certmagic.Storage, error) { // tls_enabled "false" // tls_insecure "true" // } -func (s *Storage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { +func (cs *ConsulStorage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { for d.Next() { key := d.Val() var value string @@ -72,44 +72,44 @@ func (s *Storage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if value != "" { parsedAddress, err := caddy.ParseNetworkAddress(value) if err == nil { - s.Address = parsedAddress.JoinHostPort(0) + cs.Address = parsedAddress.JoinHostPort(0) } } case "token": if value != "" { - s.Token = value + cs.Token = value } case "timeout": if value != "" { timeParse, err := strconv.Atoi(value) if err == nil { - s.Timeout = timeParse + cs.Timeout = timeParse } } case "prefix": if value != "" { - s.Prefix = value + cs.Prefix = value } case "value_prefix": if value != "" { - s.ValuePrefix = value + cs.ValuePrefix = value } case "aes_key": if value != "" { - s.AESKey = []byte(value) + cs.AESKey = []byte(value) } case "tls_enabled": if value != "" { tlsParse, err := strconv.ParseBool(value) if err == nil { - s.TlsEnabled = tlsParse + cs.TlsEnabled = tlsParse } } case "tls_insecure": if value != "" { tlsInsecureParse, err := strconv.ParseBool(value) if err == nil { - s.TlsInsecure = tlsInsecureParse + cs.TlsInsecure = tlsInsecureParse } } } diff --git a/storage.go b/storage.go index 4e6b94e..f375db5 100644 --- a/storage.go +++ b/storage.go @@ -2,7 +2,6 @@ package storageconsul import ( "context" - "fmt" "net" "path" "strings" @@ -10,11 +9,14 @@ import ( "github.com/caddyserver/certmagic" consul "github.com/hashicorp/consul/api" + "github.com/pteich/errors" "go.uber.org/zap" ) -// Storage holds all parameters for the Consul connection -type Storage struct { +// ConsulStorage allows to store certificates and other TLS resources +// in a shared cluster environment using Consul's key/value-store. +// It uses distributed locks to ensure consistency. +type ConsulStorage struct { certmagic.Storage ConsulClient *consul.Client logger *zap.SugaredLogger @@ -30,10 +32,10 @@ type Storage struct { TlsInsecure bool `json:"tls_insecure"` } -// New connects to Consul and returns a Storage -func New() *Storage { - // create Storage and pre-set values - s := Storage{ +// New connects to Consul and returns a ConsulStorage +func New() *ConsulStorage { + // create ConsulStorage and pre-set values + s := ConsulStorage{ locks: make(map[string]*consul.Lock), AESKey: []byte(DefaultAESKey), ValuePrefix: DefaultValuePrefix, @@ -44,62 +46,61 @@ func New() *Storage { return &s } -// helper function to prefix key -func (s *Storage) prefixKey(key string) string { - return path.Join(s.Prefix, key) +func (cs *ConsulStorage) prefixKey(key string) string { + return path.Join(cs.Prefix, key) } -// Lock acquires a lock for the given key or blocks until it gets it -func (s Storage) Lock(ctx context.Context, key string) error { +// Lock acquires a distributed lock for the given key or blocks until it gets one +func (cs ConsulStorage) Lock(ctx context.Context, key string) error { // if we already hold the lock, return early - if _, exists := s.locks[key]; exists { + if _, exists := cs.locks[key]; exists { return nil } // prepare the lock - lock, err := s.ConsulClient.LockKey(s.prefixKey(key)) + lock, err := cs.ConsulClient.LockKey(cs.prefixKey(key)) if err != nil { - return fmt.Errorf("could not create lock for %s: %w", s.prefixKey(key), err) + return errors.Wrapf(err, "could not create lock for %s", cs.prefixKey(key)) } // acquire the lock and return a channel that is closed upon lost lockActive, err := lock.Lock(ctx.Done()) if err != nil { - return fmt.Errorf("could not get lock for %s: %w", s.prefixKey(key), err) + return errors.Wrapf(err, "unable to lock %s", cs.prefixKey(key)) } // auto-unlock and clean list of locks in case of lost go func() { <-lockActive - s.Unlock(key) + cs.Unlock(key) }() // save the lock - s.locks[key] = lock + cs.locks[key] = lock return nil } // Unlock releases a specific lock -func (s Storage) Unlock(key string) error { +func (cs ConsulStorage) Unlock(key string) error { // check if we own it and unlock - lock, exists := s.locks[key] + lock, exists := cs.locks[key] if !exists { - return fmt.Errorf("lock %s not found", key) + return errors.Errorf("lock %s not found", cs.prefixKey(key)) } err := lock.Unlock() if err != nil { - return fmt.Errorf("unable to unlock %s: %w", key, err) + return errors.Wrapf(err, "unable to unlock %s", cs.prefixKey(key)) } - delete(s.locks, key) + delete(cs.locks, key) return nil } -// Store saves encrypted value at key in Consul KV -func (s Storage) Store(key string, value []byte) error { - kv := &consul.KVPair{Key: s.prefixKey(key)} +// Store saves encrypted data value for a key in Consul KV +func (cs ConsulStorage) Store(key string, value []byte) error { + kv := &consul.KVPair{Key: cs.prefixKey(key)} // prepare the stored data consulData := &StorageData{ @@ -107,60 +108,60 @@ func (s Storage) Store(key string, value []byte) error { Modified: time.Now(), } - encryptedValue, err := s.EncryptStorageData(consulData) + encryptedValue, err := cs.EncryptStorageData(consulData) if err != nil { - return fmt.Errorf("unable to encode data for %v: %w", key, err) + return errors.Wrapf(err, "unable to encode data for %s", cs.prefixKey(key)) } kv.Value = encryptedValue - if _, err = s.ConsulClient.KV().Put(kv, nil); err != nil { - return fmt.Errorf("unable to store data for %v: %w", key, err) + if _, err = cs.ConsulClient.KV().Put(kv, nil); err != nil { + return errors.Wrapf(err, "unable to store data for %s", cs.prefixKey(key)) } return nil } -// Load retrieves the value for key from Consul KV -func (s Storage) Load(key string) ([]byte, error) { - kv, _, err := s.ConsulClient.KV().Get(s.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) +// Load retrieves the value for a key from Consul KV +func (cs ConsulStorage) Load(key string) ([]byte, error) { + kv, _, err := cs.ConsulClient.KV().Get(cs.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) if err != nil { - return nil, fmt.Errorf("unable to obtain data for %s: %w", key, err) + return nil, errors.Wrapf(err, "unable to obtain data for %s", cs.prefixKey(key)) } else if kv == nil { - return nil, certmagic.ErrNotExist(fmt.Errorf("key %s does not exist", key)) + return nil, certmagic.ErrNotExist(errors.Errorf("key %s does not exist", cs.prefixKey(key))) } - contents, err := s.DecryptStorageData(kv.Value) + contents, err := cs.DecryptStorageData(kv.Value) if err != nil { - return nil, fmt.Errorf("unable to decrypt data for %s: %w", key, err) + return nil, errors.Wrapf(err, "unable to decrypt data for %s", cs.prefixKey(key)) } return contents.Value, nil } -// Delete a key -func (s Storage) Delete(key string) error { +// Delete a key from Consul KV +func (cs ConsulStorage) Delete(key string) error { // first obtain existing keypair - kv, _, err := s.ConsulClient.KV().Get(s.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) + kv, _, err := cs.ConsulClient.KV().Get(cs.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) if err != nil { - return fmt.Errorf("unable to obtain data for %s: %w", key, err) + return errors.Wrapf(err, "unable to obtain data for %s", cs.prefixKey(key)) } else if kv == nil { return certmagic.ErrNotExist(err) } // no do a Check-And-Set operation to verify we really deleted the key - if success, _, err := s.ConsulClient.KV().DeleteCAS(kv, nil); err != nil { - return fmt.Errorf("unable to delete data for %s: %w", key, err) + if success, _, err := cs.ConsulClient.KV().DeleteCAS(kv, nil); err != nil { + return errors.Wrapf(err, "unable to delete data for %s", cs.prefixKey(key)) } else if !success { - return fmt.Errorf("failed to lock data delete for %s", key) + return errors.Errorf("failed to lock data delete for %s", cs.prefixKey(key)) } return nil } // Exists checks if a key exists -func (s Storage) Exists(key string) bool { - kv, _, err := s.ConsulClient.KV().Get(s.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) +func (cs ConsulStorage) Exists(key string) bool { + kv, _, err := cs.ConsulClient.KV().Get(cs.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) if kv != nil && err == nil { return true } @@ -168,23 +169,23 @@ func (s Storage) Exists(key string) bool { } // List returns a list with all keys under a given prefix -func (s Storage) List(prefix string, recursive bool) ([]string, error) { +func (cs ConsulStorage) List(prefix string, recursive bool) ([]string, error) { var keysFound []string // get a list of all keys at prefix - keys, _, err := s.ConsulClient.KV().Keys(s.prefixKey(prefix), "", &consul.QueryOptions{RequireConsistent: true}) + keys, _, err := cs.ConsulClient.KV().Keys(cs.prefixKey(prefix), "", &consul.QueryOptions{RequireConsistent: true}) if err != nil { return keysFound, err } if len(keys) == 0 { - return keysFound, certmagic.ErrNotExist(fmt.Errorf("no keys at %s", prefix)) + return keysFound, certmagic.ErrNotExist(errors.Errorf("no keys at %s", prefix)) } // remove default prefix from keys for _, key := range keys { - if strings.HasPrefix(key, s.prefixKey(prefix)) { - key = strings.TrimPrefix(key, s.Prefix+"/") + if strings.HasPrefix(key, cs.prefixKey(prefix)) { + key = strings.TrimPrefix(key, cs.Prefix+"/") keysFound = append(keysFound, key) } } @@ -210,17 +211,17 @@ func (s Storage) List(prefix string, recursive bool) ([]string, error) { } // Stat returns statistic data of a key -func (s Storage) Stat(key string) (certmagic.KeyInfo, error) { - kv, _, err := s.ConsulClient.KV().Get(s.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) +func (cs ConsulStorage) Stat(key string) (certmagic.KeyInfo, error) { + kv, _, err := cs.ConsulClient.KV().Get(cs.prefixKey(key), &consul.QueryOptions{RequireConsistent: true}) if err != nil { - return certmagic.KeyInfo{}, fmt.Errorf("unable to obtain data for %s: %w", key, err) + return certmagic.KeyInfo{}, errors.Errorf("unable to obtain data for %s", cs.prefixKey(key)) } else if kv == nil { - return certmagic.KeyInfo{}, certmagic.ErrNotExist(fmt.Errorf("key %s does not exist", key)) + return certmagic.KeyInfo{}, certmagic.ErrNotExist(errors.Errorf("key %s does not exist", cs.prefixKey(key))) } - contents, err := s.DecryptStorageData(kv.Value) + contents, err := cs.DecryptStorageData(kv.Value) if err != nil { - return certmagic.KeyInfo{}, fmt.Errorf("unable to decrypt data for %s: %w", key, err) + return certmagic.KeyInfo{}, errors.Errorf("unable to decrypt data for %s", cs.prefixKey(key)) } return certmagic.KeyInfo{ @@ -231,35 +232,35 @@ func (s Storage) Stat(key string) (certmagic.KeyInfo, error) { }, nil } -func (s *Storage) createConsulClient() error { +func (cs *ConsulStorage) createConsulClient() error { // get the default config consulCfg := consul.DefaultConfig() - if s.Address != "" { - consulCfg.Address = s.Address + if cs.Address != "" { + consulCfg.Address = cs.Address } - if s.Token != "" { - consulCfg.Token = s.Token + if cs.Token != "" { + consulCfg.Token = cs.Token } - if s.TlsEnabled { + if cs.TlsEnabled { consulCfg.Scheme = "https" } - consulCfg.TLSConfig.InsecureSkipVerify = s.TlsInsecure + consulCfg.TLSConfig.InsecureSkipVerify = cs.TlsInsecure // set a dial context to prevent default keepalive consulCfg.Transport.DialContext = (&net.Dialer{ - Timeout: time.Duration(s.Timeout) * time.Second, - KeepAlive: time.Duration(s.Timeout) * time.Second, + Timeout: time.Duration(cs.Timeout) * time.Second, + KeepAlive: time.Duration(cs.Timeout) * time.Second, }).DialContext // create the Consul API client consulClient, err := consul.NewClient(consulCfg) if err != nil { - return fmt.Errorf("unable to create Consul client: %w", err) + return errors.Wrap(err, "unable to create Consul client") } if _, err := consulClient.Agent().NodeName(); err != nil { - return fmt.Errorf("unable to ping Consul: %w", err) + return errors.Wrap(err, "unable to ping Consul") } - s.ConsulClient = consulClient + cs.ConsulClient = consulClient return nil } diff --git a/storage_test.go b/storage_test.go index 762e32b..3ba0ea4 100644 --- a/storage_test.go +++ b/storage_test.go @@ -17,7 +17,7 @@ var consulClient *consul.Client const TestPrefix = "consultlstest" // these tests needs a running Consul server -func setupConsulEnv(t *testing.T) *Storage { +func setupConsulEnv(t *testing.T) *ConsulStorage { os.Setenv(EnvNamePrefix, TestPrefix) os.Setenv(consul.HTTPTokenEnvName, "2f9e03f8-714b-5e4d-65ea-c983d6b172c4")