From 0101fe80c5a596bf323bc9e4a4849d79ad496980 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Fri, 16 May 2025 18:06:12 +0530 Subject: [PATCH] Initial commit for the keymanager plugin --- go.mod | 21 +- go.sum | 60 ++- .../implementation/keymanager/cmd/plugin.go | 31 ++ .../implementation/keymanager/keymanager.go | 370 ++++++++++++++++++ 4 files changed, 478 insertions(+), 4 deletions(-) create mode 100644 pkg/plugin/implementation/keymanager/cmd/plugin.go create mode 100644 pkg/plugin/implementation/keymanager/keymanager.go diff --git a/go.mod b/go.mod index 12fad60..2e23b33 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,9 @@ require ( require github.com/stretchr/testify v1.10.0 require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/objx v0.5.2 // indirect gopkg.in/yaml.v3 v3.0.1 ) @@ -25,13 +25,30 @@ require github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 require golang.org/x/text v0.23.0 // indirect require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.31.0 // indirect + golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect ) require ( + github.com/google/uuid v1.6.0 github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/hashicorp/vault/api v1.16.0 github.com/rs/zerolog v1.34.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 821e117..c909290 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,52 @@ +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= +github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -20,32 +54,51 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +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-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +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/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -53,6 +106,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -60,5 +115,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/plugin/implementation/keymanager/cmd/plugin.go b/pkg/plugin/implementation/keymanager/cmd/plugin.go new file mode 100644 index 0000000..9326a8b --- /dev/null +++ b/pkg/plugin/implementation/keymanager/cmd/plugin.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + + "github.com/beckn/beckn-onix/pkg/log" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/keymanager" +) + +// keyManagerProvider implements the plugin provider for the KeyManager plugin. +type keyManagerProvider struct{} + +// New creates and initializes a new KeyManager instance using the provided cache, registry lookup, and configuration. +func (k *keyManagerProvider) New(ctx context.Context, cache definition.Cache, registry definition.RegistryLookup, cfg map[string]string) (definition.KeyManager, func() error, error) { + config := &keymanager.Config{ + VaultAddr: cfg["vault_addr"], + KVVersion: cfg["kv_version"], + } + log.Debugf(ctx, "Keymanager config mapped: %+v", cfg) + km, cleanup, err := keymanager.New(ctx, cache, registry, config) + if err != nil { + log.Error(ctx, err, "Failed to initialize KeyManager") + return nil, nil, err + } + log.Debugf(ctx, "KeyManager instance created successfully") + return km, cleanup, nil +} + +// Provider is the exported instance of keyManagerProvider used for plugin registration. +var Provider = keyManagerProvider{} diff --git a/pkg/plugin/implementation/keymanager/keymanager.go b/pkg/plugin/implementation/keymanager/keymanager.go new file mode 100644 index 0000000..229fa2b --- /dev/null +++ b/pkg/plugin/implementation/keymanager/keymanager.go @@ -0,0 +1,370 @@ +package keymanager + +import ( + "context" + "crypto/ecdh" + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "os" + "time" + + "github.com/beckn/beckn-onix/pkg/log" + "github.com/beckn/beckn-onix/pkg/model" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/google/uuid" + vault "github.com/hashicorp/vault/api" +) + +// Config holds configuration parameters for connecting to Vault. +type Config struct { + VaultAddr string + KVVersion string +} + +// KeyMgr provides methods for managing cryptographic keys using Vault. +type KeyMgr struct { + VaultClient *vault.Client + Registry definition.RegistryLookup + Cache definition.Cache + KvVersion string + SecretPath string +} + +var ( + // ErrEmptyKeyID indicates that the provided key ID is empty. + ErrEmptyKeyID = errors.New("invalid request: keyID cannot be empty") + + // ErrNilKeySet indicates that the provided keyset is nil. + ErrNilKeySet = errors.New("keyset cannot be nil") + + // ErrEmptySubscriberID indicates that the provided subscriber ID is empty. + ErrEmptySubscriberID = errors.New("invalid request: subscriberID cannot be empty") + + // ErrEmptyUniqueKeyID indicates that the provided unique key ID is empty. + ErrEmptyUniqueKeyID = errors.New("invalid request: uniqueKeyID cannot be empty") + + // ErrSubscriberNotFound indicates that no subscriber was found with the provided credentials. + ErrSubscriberNotFound = errors.New("no subscriber found with given credentials") + + // ErrNilCache indicates that the cache implementation is nil. + ErrNilCache = errors.New("cache implementation cannot be nil") + + // ErrNilRegistryLookup indicates that the registry lookup implementation is nil. + ErrNilRegistryLookup = errors.New("registry lookup implementation cannot be nil") +) + +// ValidateCfg validates the Vault configuration and sets default KV version if missing. +func ValidateCfg(cfg *Config) error { + if cfg.VaultAddr == "" { + return errors.New("invalid config: VaultAddr cannot be empty") + } + if cfg.KVVersion == "" { + cfg.KVVersion = "v1" + } else if cfg.KVVersion != "v1" && cfg.KVVersion != "v2" { + return fmt.Errorf("invalid KVVersion: must be 'v1' or 'v2'") + } + return nil +} + +func New(ctx context.Context, cache definition.Cache, registryLookup definition.RegistryLookup, cfg *Config) (*KeyMgr, func() error, error) { + log.Info(ctx, "Initializing KeyManager plugin") + // Validate configuration. + if err := ValidateCfg(cfg); err != nil { + log.Error(ctx, err, "Invalid configuration for KeyManager") + return nil, nil, err + } + // Check if cache implementation is provided. + if cache == nil { + log.Error(ctx, ErrNilCache, "Cache is nil in KeyManager initialization") + return nil, nil, ErrNilCache + } + + // Check if registry lookup implementation is provided. + if registryLookup == nil { + log.Error(ctx, ErrNilRegistryLookup, "RegistryLookup is nil in KeyManager initialization") + return nil, nil, ErrNilRegistryLookup + } + + // Initialize Vault client. + log.Debugf(ctx, "Creating Vault client with address: %s", cfg.VaultAddr) + vaultClient, err := GetVaultClient(ctx, cfg.VaultAddr) + if err != nil { + log.Errorf(ctx, err, "Failed to create Vault client at address: %s", cfg.VaultAddr) + return nil, nil, fmt.Errorf("failed to create vault client: %w", err) + } + + log.Info(ctx, "Successfully created Vault client") + + // Create KeyManager instance. + km := &KeyMgr{ + VaultClient: vaultClient, + Registry: registryLookup, + Cache: cache, + KvVersion: cfg.KVVersion, + } + + // Cleanup function to release KeyManager resources. + cleanup := func() error { + log.Info(ctx, "Cleaning up KeyManager resources") + km.VaultClient = nil + km.Cache = nil + km.Registry = nil + return nil + } + + log.Info(ctx, "KeyManager plugin initialized successfully") + return km, cleanup, nil +} + +// GetVaultClient creates and authenticates a Vault client using AppRole. +func GetVaultClient(ctx context.Context, vaultAddr string) (*vault.Client, error) { + roleID := os.Getenv("VAULT_ROLE_ID") + secretID := os.Getenv("VAULT_SECRET_ID") + + if roleID == "" || secretID == "" { + log.Error(ctx, fmt.Errorf("missing credentials"), "VAULT_ROLE_ID or VAULT_SECRET_ID is not set") + return nil, fmt.Errorf("VAULT_ROLE_ID or VAULT_SECRET_ID is not set") + } + + config := vault.DefaultConfig() + config.Address = vaultAddr + + client, err := vault.NewClient(config) + if err != nil { + log.Error(ctx, err, "failed to create Vault client") + return nil, fmt.Errorf("failed to create Vault client: %w", err) + } + + data := map[string]interface{}{ + "role_id": roleID, + "secret_id": secretID, + } + + log.Info(ctx, "Logging into Vault with AppRole") + resp, err := client.Logical().Write("auth/approle/login", data) + if err != nil { + log.Error(ctx, err, "failed to login with AppRole") + return nil, fmt.Errorf("failed to login with AppRole: %w", err) + } + if resp == nil || resp.Auth == nil { + log.Error(ctx, nil, "AppRole login failed: no auth info returned") + return nil, errors.New("AppRole login failed: no auth info returned") + } + + log.Info(ctx, "Vault login successful") + client.SetToken(resp.Auth.ClientToken) + return client, nil +} + +// GenerateKeyPairs generates a new signing (Ed25519) and encryption (X25519) key pair. +func (km *KeyMgr) GenerateKeyPairs() (*model.Keyset, error) { + signingPublic, signingPrivate, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate signing key pair: %w", err) + } + + encrPrivateKey, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate encryption key pair: %w", err) + } + encrPublicKey := encrPrivateKey.PublicKey().Bytes() + uuid, err := uuid.NewRandom() + if err != nil { + return nil, fmt.Errorf("failed to generate unique key id uuid: %w", err) + } + return &model.Keyset{ + UniqueKeyID: uuid.String(), + SigningPrivate: encodeBase64(signingPrivate.Seed()), + SigningPublic: encodeBase64(signingPublic), + EncrPrivate: encodeBase64(encrPrivateKey.Bytes()), + EncrPublic: encodeBase64(encrPublicKey), + }, nil +} + +// StorePrivateKeys stores the given keyset in Vault under the specified key ID. +func (km *KeyMgr) StorePrivateKeys(ctx context.Context, keyID string, keys *model.Keyset) error { + if keyID == "" { + return ErrEmptyKeyID + } + if keys == nil { + return ErrNilKeySet + } + + keyData := map[string]interface{}{ + "uniqueKeyID": keys.UniqueKeyID, + "signingPublicKey": keys.SigningPublic, + "signingPrivateKey": keys.SigningPrivate, + "encrPublicKey": keys.EncrPublic, + "encrPrivateKey": keys.EncrPrivate, + } + var path string + var payload map[string]interface{} + if km.KvVersion == "v2" { + path = fmt.Sprintf("secret/data/keys/%s", keyID) + payload = map[string]interface{}{"data": keyData} + } else { + path = fmt.Sprintf("secret/keys/%s", keyID) + payload = keyData + } + + _, err := km.VaultClient.Logical().Write(path, payload) + if err != nil { + return fmt.Errorf("failed to store secret in Vault: %w", err) + } + return nil +} + +// SigningPrivateKey retrieves the unique key ID and signing private key for the given key ID. +func (km *KeyMgr) SigningPrivateKey(ctx context.Context, keyID string) (string, string, error) { + keys, err := km.getKeys(ctx, keyID) + if err != nil { + return "", "", err + } + return keys.UniqueKeyID, keys.SigningPrivate, nil +} + +// EncrPrivateKey retrieves the unique key ID and encryption private key for the given key ID. +func (km *KeyMgr) EncrPrivateKey(ctx context.Context, keyID string) (string, string, error) { + keys, err := km.getKeys(ctx, keyID) + if err != nil { + return "", "", err + } + return keys.UniqueKeyID, keys.EncrPrivate, nil +} + +// SigningPublicKey returns the signing public key for the given subscriber ID and key ID. +func (km *KeyMgr) SigningPublicKey(ctx context.Context, subscriberID, uniqueKeyID string) (string, error) { + keys, err := km.getPublicKeys(ctx, subscriberID, uniqueKeyID) + if err != nil { + return "", err + } + return keys.SigningPublic, nil +} + +// EncrPublicKey returns the encryption public key for the given subscriber ID and key ID. +func (km *KeyMgr) EncrPublicKey(ctx context.Context, subscriberID, uniqueKeyID string) (string, error) { + keys, err := km.getPublicKeys(ctx, subscriberID, uniqueKeyID) + if err != nil { + return "", err + } + return keys.EncrPublic, nil +} + +// DeletePrivateKeys deletes the private keys for the given key ID from Vault. +func (km *KeyMgr) DeletePrivateKeys(ctx context.Context, keyID string) error { + if keyID == "" { + return ErrEmptyKeyID + } + var path string + if km.KvVersion == "v2" { + path = fmt.Sprintf("secret/data/private_keys/%s", keyID) + } else { + path = fmt.Sprintf("secret/private_keys/%s", keyID) + } + return km.VaultClient.KVv2(path).Delete(ctx, keyID) +} + +// getKeys retrieves the full keyset from Vault for the given key ID. +func (km *KeyMgr) getKeys(ctx context.Context, keyID string) (*model.Keyset, error) { + if keyID == "" { + return nil, ErrEmptyKeyID + } + + var path string + if km.KvVersion == "v2" { + path = fmt.Sprintf("secret/data/private_keys/%s", keyID) + } else { + path = fmt.Sprintf("secret/private_keys/%s", keyID) + } + + secret, err := km.VaultClient.Logical().Read(path) + if err != nil || secret == nil { + return nil, fmt.Errorf("failed to read secret from Vault: %w", err) + } + + var data map[string]interface{} + if km.KvVersion == "v2" { + dataRaw, ok := secret.Data["data"] + if !ok { + return nil, errors.New("missing 'data' in secret response") + } + data, ok = dataRaw.(map[string]interface{}) + if !ok { + return nil, errors.New("invalid 'data' format in Vault response") + } + } else { + data = secret.Data + } + + return &model.Keyset{ + UniqueKeyID: data["uniqueKeyID"].(string), + SigningPublic: data["signingPublicKey"].(string), + SigningPrivate: data["signingPrivateKey"].(string), + EncrPublic: data["encrPublicKey"].(string), + EncrPrivate: data["encrPrivateKey"].(string), + }, nil +} + +// getPublicKeys fetches the public keys from cache or registry for the given subscriber and key ID. +func (km *KeyMgr) getPublicKeys(ctx context.Context, subscriberID, uniqueKeyID string) (*model.Keyset, error) { + if err := validateParams(subscriberID, uniqueKeyID); err != nil { + return nil, err + } + cacheKey := fmt.Sprintf("%s_%s", subscriberID, uniqueKeyID) + cachedData, err := km.Cache.Get(ctx, cacheKey) + if err == nil { + var keys model.Keyset + if err := json.Unmarshal([]byte(cachedData), &keys); err == nil { + return &keys, nil + } + } + publicKeys, err := km.lookupRegistry(ctx, subscriberID, uniqueKeyID) + if err != nil { + return nil, err + } + cacheValue, err := json.Marshal(publicKeys) + if err == nil { + _ = km.Cache.Set(ctx, cacheKey, string(cacheValue), time.Hour) + } + return publicKeys, nil +} + +// lookupRegistry queries the registry for public keys based on subscriber ID and key ID. +func (km *KeyMgr) lookupRegistry(ctx context.Context, subscriberID, uniqueKeyID string) (*model.Keyset, error) { + subscribers, err := km.Registry.Lookup(ctx, &model.Subscription{ + Subscriber: model.Subscriber{ + SubscriberID: subscriberID, + }, + KeyID: uniqueKeyID, + }) + if err != nil { + return nil, fmt.Errorf("failed to lookup registry: %w", err) + } + if len(subscribers) == 0 { + return nil, ErrSubscriberNotFound + } + return &model.Keyset{ + SigningPublic: subscribers[0].SigningPublicKey, + EncrPublic: subscribers[0].EncrPublicKey, + }, nil +} + +// validateParams checks that subscriberID and uniqueKeyID are not empty. +func validateParams(subscriberID, uniqueKeyID string) error { + if subscriberID == "" { + return ErrEmptySubscriberID + } + if uniqueKeyID == "" { + return ErrEmptyUniqueKeyID + } + return nil +} + +// encodeBase64 returns the base64-encoded string of the given data. +func encodeBase64(data []byte) string { + return base64.StdEncoding.EncodeToString(data) +}