commit 224ad208f1a4e4f41afc3f854264b3ebd27054d3 Author: Konstantin Demin Date: Fri Jun 7 07:29:07 2024 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b77702d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vault-usage diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..47936d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,69 @@ +#!/usr/bin/make -f +# SPDX-License-Identifier: Apache-2.0 +# (c) 2024, Konstantin Demin + +SHELL :=/bin/sh +.SHELLFLAGS :=-ec + +.NOTPARALLEL: + +BIN := vault-usage + +OUTDIR ?= . +OUTSFX ?= +OUTBIN ?= $(OUTDIR)/$(BIN)$(OUTSFX) + +export GO ?= go +export CGO_ENABLED ?= 0 +TAGS ?= +LDFLAGS ?= +GO_BUILDFLAGS ?= +GO_LDFLAGS := -w $(LDFLAGS) + +comma :=, +ifeq ($(RELMODE),1) + ## not ready yet + # TAGS := nodebug$(if $(strip $(TAGS)),$(comma)$(strip $(TAGS))) + GO_LDFLAGS += -s +endif + +.PHONY: all +all: build + +.PHONY: clean build dev-build ci-clean + +clean: + $(if $(wildcard $(OUTBIN)),rm -fv $(OUTBIN),:) + +build: $(OUTBIN) + +test_git = git -c log.showsignature=false show -s --format=%H:%ct + +$(OUTBIN): + @:; \ + GO_BUILDFLAGS='$(strip $(GO_BUILDFLAGS))' ; \ + if ! $(test_git) >/dev/null 2>&1 ; then \ + echo "!!! git information is asbent !!!" >&2 ; \ + GO_BUILDFLAGS="-buildvcs=false $${GO_BUILDFLAGS}" ; \ + fi ; \ + for i in $$(seq 1 3) ; do \ + if $(GO) get ; then break ; fi ; \ + done ; \ + $(GO) build -o $@ \ + $${GO_BUILDFLAGS} \ + $(if $(strip $(TAGS)),-tags '$(strip $(TAGS))') \ + $(if $(strip $(GO_LDFLAGS)),-ldflags '$(strip $(GO_LDFLAGS))') \ + $(if $(VERBOSE),-v) ; \ + $(GO) version -m $@ + +dev-build: GO_BUILDFLAGS := -race $(GO_BUILDFLAGS) +dev-build: CGO_ENABLED := 1 +dev-build: RELMODE := 0 +dev-build: build + +ci-clean: + for d in '$(shell $(GO) env GOCACHE)' '$(shell $(GO) env GOMODCACHE)' ; do \ + [ -n "$$d" ] || continue ; \ + [ -d "$$d" ] || continue ; \ + rm -rf "$$d" ; \ + done diff --git a/example-conf/nginx.conf b/example-conf/nginx.conf new file mode 100644 index 0000000..4a50fdf --- /dev/null +++ b/example-conf/nginx.conf @@ -0,0 +1,21 @@ +upstream vault { server 127.0.0.1:8200 } +upstream vault_usage { server 127.0.0.1:3000 } + +server { + location / { + proxy_pass http://vault; + } + location /v1/ { + proxy_pass http://vault; + mirror /mirror-vault-usage; + mirror_request_body off; + } + location = /mirror-vault-usage { + internal; + proxy_pass http://vault_usage; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Vault-Token ""; + proxy_http_version 1.1; + } +} diff --git a/fiber.go b/fiber.go new file mode 100644 index 0000000..c0acc00 --- /dev/null +++ b/fiber.go @@ -0,0 +1,63 @@ +package main + +import ( + "os" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" +) + +const ( + MethodList = "LIST" +) + +var ( + fiberConfig = fiber.Config{ + AppName: userAgent, + ServerHeader: userAgent, + StrictRouting: true, + DisableDefaultDate: true, + DisablePreParseMultipartForm: true, + ErrorHandler: fiberErrorHandler, + } +) + +func initFiber() *fiber.App { + // adjust with Hashicorp Vault HTTP API + haveListMethod := false + for _, m := range fiber.DefaultMethods { + if m == MethodList { + haveListMethod = true + break + } + } + if !haveListMethod { + fiberConfig.RequestMethods = append(fiber.DefaultMethods, MethodList) + } + + _, debug := os.LookupEnv("DEBUG") + if debug { + fiberConfig.EnablePrintRoutes = true + } + + app := fiber.New(fiberConfig) + app.Use(logger.New(logger.Config{ + TimeInterval: 100 * time.Millisecond, + })) + return app +} + +func fiberOk(c *fiber.Ctx) error { + c.Status(fiber.StatusOK) + return nil +} + +func fiberNone(c *fiber.Ctx) error { + c.Status(fiber.StatusNoContent) + return nil +} + +func fiberErrorHandler(c *fiber.Ctx, err error) error { + return fiberNone(c) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d7620c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module git.krd.sh/krd/vault-usage-example + +go 1.22 + +require ( + github.com/gofiber/fiber/v2 v2.52.4 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.54.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.21.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..adcc5c1 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +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/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0= +github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..b7b854a --- /dev/null +++ b/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "log" + "runtime" +) + +const ( + appName = "vault-usage" + appVersion = "0.0.1" + userAgent = appName + "/" + appVersion + + minimumGoMaxProcs = 4 +) + +var ( + apiRootUri string + listenEndpoint string +) + +func init() { + flag.StringVar(&apiRootUri, "api-root-uri", "/v1", "API root URI") + flag.StringVar(&listenEndpoint, "listen", ":3000", "listen (\":port\", \"address:port\")") +} + +func main() { + gmp := runtime.GOMAXPROCS(0) + if gmp < minimumGoMaxProcs { + runtime.GOMAXPROCS(minimumGoMaxProcs) + } + + log.SetFlags(log.Flags() | log.Lmicroseconds) + flag.Parse() + + log.Printf("%s: starting\n", userAgent) + + app := initFiber() + setupVaultApi(app.Group(apiRootUri)) + + log.Printf("%s: ready\n", userAgent) + if err := app.Listen(listenEndpoint); err != nil { + log.Fatal(err) + } +} diff --git a/vault-api.go b/vault-api.go new file mode 100644 index 0000000..363dbcd --- /dev/null +++ b/vault-api.go @@ -0,0 +1,68 @@ +package main + +import ( + "github.com/gofiber/fiber/v2" +) + +const ( + uriSecretData = "/:secret/data/:path" + uriSecretMetadata = "/:secret/metadata/:path" +) + +func setupVaultApi(router fiber.Router) { + // https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2 + + /* read secret */ + router.Get(uriSecretData, fiberOk, func(c *fiber.Ctx) error { + /* + ver_s := c.Query("version") + if ver_s != "" { + ver, err := strconv.ParseInt(ver_s, 10, 0) + if err != nil { + c.Status(fiber.StatusBadRequest) + return nil + } + // do job with ver + } + */ + return nil + }) + /* create/update/patch secret */ + router.Post(uriSecretData, fiberOk) + router.Patch(uriSecretData, fiberOk) + /* delete secret */ + router.Delete(uriSecretData, fiberOk) + + /* delete/undelete secret */ + router.Post("/:secret/delete/:path", fiberOk) + router.Post("/:secret/undelete/:path", fiberOk) + /* destroy secret */ + router.Post("/:secret/destroy/:path", fiberOk) + + /* read subkeys */ + // router.Get("/:secret/subkeys/:path?version=:version&depth=:depth", fiberOk) + router.Get("/:secret/subkeys/:path", fiberOk, func(c *fiber.Ctx) error { + /* + ver_s := c.Query("version") + if ver_s != "" { + ver, err := strconv.ParseInt(ver_s, 10, 0) + if err != nil { + c.Status(fiber.StatusBadRequest) + return nil + } + // do job with ver + } + */ + return nil + }) + + /* read metadata */ + router.Get(uriSecretMetadata, fiberOk) + /* create/update metadata */ + router.Post(uriSecretMetadata, fiberOk) + router.Patch(uriSecretMetadata, fiberOk) + /* delete metadata */ + router.Delete(uriSecretMetadata, fiberOk) + /* list secrets */ + router.Add(MethodList, uriSecretMetadata, fiberOk) +}