lex-vault 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 526747f6bb118dd5ab02d67692f4ee8bc76420f3a061ae05a31efab4ec8617ca
4
+ data.tar.gz: 7310bf9354246b6997d10e208d9dae190fd205bfea113a67d88676f98f8cd3a6
5
+ SHA512:
6
+ metadata.gz: 359d0220a0aaf429cb445e91e6faca5a7ff9e8720efd37ca5e61145af56b3db118370f1e9fb994495e8c6efbef5cbb33a2d91e64e4dab66d5372798a8c6b4563
7
+ data.tar.gz: dba4904895ccf4637fafc5a3bc35fc9a27d448b410ffbfb0d47c6bca1d5c659ac23d00c6fc81d13362e46509c8d1449c3d00077edb159f066176488311bcd888
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,53 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.4
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Layout/LineLength:
7
+ Max: 160
8
+
9
+ Layout/SpaceAroundEqualsInParameterDefault:
10
+ EnforcedStyle: space
11
+
12
+ Layout/HashAlignment:
13
+ EnforcedHashRocketStyle: table
14
+ EnforcedColonStyle: table
15
+
16
+ Metrics/MethodLength:
17
+ Max: 50
18
+
19
+ Metrics/ClassLength:
20
+ Max: 1500
21
+
22
+ Metrics/ModuleLength:
23
+ Max: 1500
24
+
25
+ Metrics/BlockLength:
26
+ Max: 40
27
+ Exclude:
28
+ - 'spec/**/*_spec.rb'
29
+
30
+ Metrics/ParameterLists:
31
+ Max: 8
32
+
33
+ Metrics/AbcSize:
34
+ Max: 60
35
+
36
+ Metrics/CyclomaticComplexity:
37
+ Max: 15
38
+
39
+ Metrics/PerceivedComplexity:
40
+ Max: 17
41
+
42
+ Style/Documentation:
43
+ Enabled: false
44
+
45
+ Style/SymbolArray:
46
+ Enabled: true
47
+
48
+ Style/FrozenStringLiteralComment:
49
+ Enabled: true
50
+ EnforcedStyle: always
51
+
52
+ Naming/FileName:
53
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-03-13
4
+
5
+ ### Added
6
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,50 @@
1
+ # lex-vault: HashiCorp Vault Integration for LegionIO
2
+
3
+ **Repository Level 3 Documentation**
4
+ - **Parent (Level 2)**: `/Users/miverso2/rubymine/legion/extensions/CLAUDE.md`
5
+ - **Parent (Level 1)**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
6
+
7
+ ## Purpose
8
+
9
+ Legion Extension that connects LegionIO to HashiCorp Vault. Provides runners for system operations, KV v2 secrets, token auth, Transit encryption, PKI certificates, and lease management.
10
+
11
+ **GitHub**: https://github.com/LegionIO/lex-vault
12
+ **License**: MIT
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ Legion::Extensions::Vault
18
+ ├── Runners/
19
+ │ ├── System # Health, seal/unseal, mounts, auth, policies
20
+ │ ├── Kv # KV v2: read/write/delete/list secrets + metadata
21
+ │ ├── Token # Token auth: create/lookup/renew/revoke + roles
22
+ │ ├── Transit # Encrypt/decrypt/sign/verify/hash/random
23
+ │ ├── Pki # Issue/sign/revoke certs, CA chain, roles
24
+ │ └── Leases # Lookup/renew/revoke leases
25
+ ├── Helpers/
26
+ │ └── Client # Faraday connection builder (X-Vault-Token + namespace)
27
+ └── Client # Standalone client class (includes all runners)
28
+ ```
29
+
30
+ ## Dependencies
31
+
32
+ | Gem | Purpose |
33
+ |-----|---------|
34
+ | `faraday` | HTTP client for Vault REST API |
35
+
36
+ ## Connection
37
+
38
+ The client accepts `address` (matching Vault's `VAULT_ADDR`), `token`, and optional `namespace` (Enterprise). All API calls go through `/v1/` prefix.
39
+
40
+ ## Testing
41
+
42
+ ```bash
43
+ bundle install
44
+ bundle exec rspec
45
+ bundle exec rubocop
46
+ ```
47
+
48
+ ---
49
+
50
+ **Maintained By**: Matthew Iverson (@Esity)
data/Dockerfile ADDED
@@ -0,0 +1,6 @@
1
+ FROM legionio/legion
2
+
3
+ COPY . /usr/src/app/lex-vault
4
+
5
+ WORKDIR /usr/src/app/lex-vault
6
+ RUN bundle install
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ gem 'rspec_junit_formatter'
10
+ gem 'rubocop'
11
+ gem 'simplecov'
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,98 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lex-vault (0.1.0)
5
+ faraday (>= 2.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.8.9)
11
+ public_suffix (>= 2.0.2, < 8.0)
12
+ ast (2.4.3)
13
+ bigdecimal (4.0.1)
14
+ diff-lcs (1.6.2)
15
+ docile (1.4.1)
16
+ faraday (2.14.1)
17
+ faraday-net_http (>= 2.0, < 3.5)
18
+ json
19
+ logger
20
+ faraday-net_http (3.4.2)
21
+ net-http (~> 0.5)
22
+ json (2.19.1)
23
+ json-schema (6.2.0)
24
+ addressable (~> 2.8)
25
+ bigdecimal (>= 3.1, < 5)
26
+ language_server-protocol (3.17.0.5)
27
+ lint_roller (1.1.0)
28
+ logger (1.7.0)
29
+ mcp (0.8.0)
30
+ json-schema (>= 4.1)
31
+ net-http (0.9.1)
32
+ uri (>= 0.11.1)
33
+ parallel (1.27.0)
34
+ parser (3.3.10.2)
35
+ ast (~> 2.4.1)
36
+ racc
37
+ prism (1.9.0)
38
+ public_suffix (7.0.5)
39
+ racc (1.8.1)
40
+ rainbow (3.1.1)
41
+ rake (13.3.1)
42
+ regexp_parser (2.11.3)
43
+ rspec (3.13.2)
44
+ rspec-core (~> 3.13.0)
45
+ rspec-expectations (~> 3.13.0)
46
+ rspec-mocks (~> 3.13.0)
47
+ rspec-core (3.13.6)
48
+ rspec-support (~> 3.13.0)
49
+ rspec-expectations (3.13.5)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.13.0)
52
+ rspec-mocks (3.13.8)
53
+ diff-lcs (>= 1.2.0, < 2.0)
54
+ rspec-support (~> 3.13.0)
55
+ rspec-support (3.13.7)
56
+ rspec_junit_formatter (0.6.0)
57
+ rspec-core (>= 2, < 4, != 2.12.0)
58
+ rubocop (1.85.1)
59
+ json (~> 2.3)
60
+ language_server-protocol (~> 3.17.0.2)
61
+ lint_roller (~> 1.1.0)
62
+ mcp (~> 0.6)
63
+ parallel (~> 1.10)
64
+ parser (>= 3.3.0.2)
65
+ rainbow (>= 2.2.2, < 4.0)
66
+ regexp_parser (>= 2.9.3, < 3.0)
67
+ rubocop-ast (>= 1.49.0, < 2.0)
68
+ ruby-progressbar (~> 1.7)
69
+ unicode-display_width (>= 2.4.0, < 4.0)
70
+ rubocop-ast (1.49.1)
71
+ parser (>= 3.3.7.2)
72
+ prism (~> 1.7)
73
+ ruby-progressbar (1.13.0)
74
+ simplecov (0.22.0)
75
+ docile (~> 1.1)
76
+ simplecov-html (~> 0.11)
77
+ simplecov_json_formatter (~> 0.1)
78
+ simplecov-html (0.13.2)
79
+ simplecov_json_formatter (0.1.4)
80
+ unicode-display_width (3.2.0)
81
+ unicode-emoji (~> 4.1)
82
+ unicode-emoji (4.2.0)
83
+ uri (1.1.1)
84
+
85
+ PLATFORMS
86
+ arm64-darwin-25
87
+ ruby
88
+
89
+ DEPENDENCIES
90
+ lex-vault!
91
+ rake
92
+ rspec
93
+ rspec_junit_formatter
94
+ rubocop
95
+ simplecov
96
+
97
+ BUNDLED WITH
98
+ 2.6.9
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Esity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # lex-vault
2
+
3
+ HashiCorp Vault integration for [LegionIO](https://github.com/LegionIO/LegionIO). Provides runners for interacting with the Vault HTTP API covering system operations, KV v2 secrets, token auth, Transit encryption, PKI certificates, and lease management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install lex-vault
9
+ ```
10
+
11
+ ## Functions
12
+
13
+ ### System
14
+ `health`, `seal_status`, `seal`, `unseal`, `init`, `list_mounts`, `mount`, `unmount`, `list_auth`, `enable_auth`, `disable_auth`, `list_policies`, `get_policy`, `put_policy`, `delete_policy`
15
+
16
+ ### KV (v2 Secrets)
17
+ `read_secret`, `write_secret`, `patch_secret`, `delete_secret`, `delete_versions`, `undelete_versions`, `destroy_versions`, `list_secrets`, `read_metadata`, `write_metadata`, `delete_metadata`
18
+
19
+ ### Token
20
+ `create_token`, `lookup_token`, `lookup_self`, `renew_token`, `renew_self`, `revoke_token`, `revoke_self`, `list_accessors`, `list_token_roles`, `get_token_role`, `create_token_role`, `delete_token_role`
21
+
22
+ ### Transit
23
+ `create_key`, `read_key`, `list_keys`, `delete_key`, `rotate_key`, `encrypt`, `decrypt`, `rewrap`, `datakey`, `sign`, `verify`, `hash_data`, `random_bytes`
24
+
25
+ ### PKI
26
+ `issue`, `sign_csr`, `revoke`, `list_certs`, `get_cert`, `ca_chain`, `list_pki_roles`, `get_pki_role`, `create_pki_role`, `tidy`
27
+
28
+ ### Leases
29
+ `lookup_lease`, `renew_lease`, `revoke_lease`, `list_leases`
30
+
31
+ ## Standalone Usage
32
+
33
+ ```ruby
34
+ require 'legion/extensions/vault'
35
+
36
+ client = Legion::Extensions::Vault::Client.new(
37
+ address: 'https://vault.example.com',
38
+ token: 'hvs.your-token',
39
+ namespace: 'admin/team1' # optional, Enterprise only
40
+ )
41
+
42
+ # Read a secret
43
+ client.read_secret(path: 'myapp/config')
44
+
45
+ # Write a secret
46
+ client.write_secret(path: 'myapp/config', data: { api_key: 'secret123' })
47
+
48
+ # Encrypt with Transit
49
+ client.encrypt(name: 'my-key', plaintext: Base64.strict_encode64('hello'))
50
+
51
+ # Issue a PKI certificate
52
+ client.issue(role: 'web-server', common_name: 'app.example.com')
53
+ ```
54
+
55
+ ## Requirements
56
+
57
+ - Ruby >= 3.4
58
+ - [LegionIO](https://github.com/LegionIO/LegionIO) framework (optional for standalone client usage)
59
+ - HashiCorp Vault server (any version with HTTP API v1)
60
+ - `faraday` >= 2.0
61
+
62
+ ## License
63
+
64
+ MIT
data/lex-vault.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/vault/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-vault'
7
+ spec.version = Legion::Extensions::Vault::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Vault'
12
+ spec.description = 'Connects LegionIO to HashiCorp Vault'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-vault'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-vault'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-vault'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-vault'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-vault/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'faraday', '>= 2.0'
30
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+ require 'legion/extensions/vault/runners/system'
5
+ require 'legion/extensions/vault/runners/kv'
6
+ require 'legion/extensions/vault/runners/token'
7
+ require 'legion/extensions/vault/runners/transit'
8
+ require 'legion/extensions/vault/runners/pki'
9
+ require 'legion/extensions/vault/runners/leases'
10
+
11
+ module Legion
12
+ module Extensions
13
+ module Vault
14
+ class Client
15
+ include Helpers::Client
16
+ include Runners::System
17
+ include Runners::Kv
18
+ include Runners::Token
19
+ include Runners::Transit
20
+ include Runners::Pki
21
+ include Runners::Leases
22
+
23
+ attr_reader :opts
24
+
25
+ def initialize(address: 'http://127.0.0.1:8200', token: nil, namespace: nil, **extra)
26
+ @opts = { address: address, token: token, namespace: namespace, **extra }
27
+ end
28
+
29
+ def connection(**override)
30
+ super(**@opts.merge(override))
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Helpers
9
+ module Client
10
+ def connection(address: 'http://127.0.0.1:8200', token: nil, namespace: nil, **_opts)
11
+ Faraday.new(url: address) do |conn|
12
+ conn.request :json
13
+ conn.response :json, content_type: /\bjson$/
14
+ conn.headers['X-Vault-Token'] = token if token
15
+ conn.headers['X-Vault-Namespace'] = namespace if namespace
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Runners
9
+ module Kv
10
+ include Legion::Extensions::Vault::Helpers::Client
11
+
12
+ def read_secret(path:, mount: 'secret', version: nil, **)
13
+ url = "/v1/#{mount}/data/#{path}"
14
+ params = version ? { version: version } : {}
15
+ response = connection(**).get(url, params)
16
+ { result: response.body }
17
+ end
18
+
19
+ def write_secret(path:, data:, mount: 'secret', cas: nil, **)
20
+ url = "/v1/#{mount}/data/#{path}"
21
+ body = { data: data }
22
+ body[:options] = { cas: cas } if cas
23
+ response = connection(**).post(url, body)
24
+ { result: response.body }
25
+ end
26
+
27
+ def patch_secret(path:, data:, mount: 'secret', **)
28
+ url = "/v1/#{mount}/data/#{path}"
29
+ conn = connection(**)
30
+ conn.headers['Content-Type'] = 'application/merge-patch+json'
31
+ response = conn.patch(url, { data: data })
32
+ { result: response.body }
33
+ end
34
+
35
+ def delete_secret(path:, mount: 'secret', **)
36
+ response = connection(**).delete("/v1/#{mount}/data/#{path}")
37
+ { result: response.status == 204 }
38
+ end
39
+
40
+ def delete_versions(path:, versions:, mount: 'secret', **)
41
+ response = connection(**).post("/v1/#{mount}/delete/#{path}", { versions: versions })
42
+ { result: response.status == 204 }
43
+ end
44
+
45
+ def undelete_versions(path:, versions:, mount: 'secret', **)
46
+ response = connection(**).post("/v1/#{mount}/undelete/#{path}", { versions: versions })
47
+ { result: response.status == 204 }
48
+ end
49
+
50
+ def destroy_versions(path:, versions:, mount: 'secret', **)
51
+ response = connection(**).put("/v1/#{mount}/destroy/#{path}", { versions: versions })
52
+ { result: response.status == 204 }
53
+ end
54
+
55
+ def list_secrets(path:, mount: 'secret', **)
56
+ response = connection(**).get("/v1/#{mount}/metadata/#{path}", { list: true })
57
+ { result: response.body }
58
+ end
59
+
60
+ def read_metadata(path:, mount: 'secret', **)
61
+ response = connection(**).get("/v1/#{mount}/metadata/#{path}")
62
+ { result: response.body }
63
+ end
64
+
65
+ def write_metadata(path:, mount: 'secret', max_versions: nil, cas_required: nil, delete_version_after: nil, **)
66
+ body = { max_versions: max_versions, cas_required: cas_required,
67
+ delete_version_after: delete_version_after }.compact
68
+ response = connection(**).post("/v1/#{mount}/metadata/#{path}", body)
69
+ { result: response.status == 204 }
70
+ end
71
+
72
+ def delete_metadata(path:, mount: 'secret', **)
73
+ response = connection(**).delete("/v1/#{mount}/metadata/#{path}")
74
+ { result: response.status == 204 }
75
+ end
76
+
77
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
78
+ Legion::Extensions::Helpers.const_defined?(:Lex)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Runners
9
+ module Leases
10
+ include Legion::Extensions::Vault::Helpers::Client
11
+
12
+ def lookup_lease(lease_id:, **)
13
+ response = connection(**).put('/v1/sys/leases/lookup', { lease_id: lease_id })
14
+ { result: response.body }
15
+ end
16
+
17
+ def renew_lease(lease_id:, increment: nil, **)
18
+ body = { lease_id: lease_id, increment: increment }.compact
19
+ response = connection(**).put('/v1/sys/leases/renew', body)
20
+ { result: response.body }
21
+ end
22
+
23
+ def revoke_lease(lease_id:, **)
24
+ response = connection(**).put('/v1/sys/leases/revoke', { lease_id: lease_id })
25
+ { result: response.status == 204 }
26
+ end
27
+
28
+ def list_leases(prefix:, **)
29
+ response = connection(**).get("/v1/sys/leases/lookup/#{prefix}", { list: true })
30
+ { result: response.body }
31
+ end
32
+
33
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
34
+ Legion::Extensions::Helpers.const_defined?(:Lex)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Runners
9
+ module Pki
10
+ include Legion::Extensions::Vault::Helpers::Client
11
+
12
+ def issue(role:, common_name:, mount: 'pki', alt_names: nil, ttl: nil, **)
13
+ body = { common_name: common_name, alt_names: alt_names, ttl: ttl }.compact
14
+ response = connection(**).post("/v1/#{mount}/issue/#{role}", body)
15
+ { result: response.body }
16
+ end
17
+
18
+ def sign_csr(role:, csr:, common_name:, mount: 'pki', alt_names: nil, ttl: nil, **)
19
+ body = { csr: csr, common_name: common_name, alt_names: alt_names, ttl: ttl }.compact
20
+ response = connection(**).post("/v1/#{mount}/sign/#{role}", body)
21
+ { result: response.body }
22
+ end
23
+
24
+ def revoke(serial_number:, mount: 'pki', **)
25
+ response = connection(**).post("/v1/#{mount}/revoke", { serial_number: serial_number })
26
+ { result: response.body }
27
+ end
28
+
29
+ def list_certs(mount: 'pki', **)
30
+ response = connection(**).get("/v1/#{mount}/certs", { list: true })
31
+ { result: response.body }
32
+ end
33
+
34
+ def get_cert(serial:, mount: 'pki', **)
35
+ response = connection(**).get("/v1/#{mount}/cert/#{serial}")
36
+ { result: response.body }
37
+ end
38
+
39
+ def ca_chain(mount: 'pki', **)
40
+ response = connection(**).get("/v1/#{mount}/ca_chain")
41
+ { result: response.body }
42
+ end
43
+
44
+ def list_pki_roles(mount: 'pki', **)
45
+ response = connection(**).get("/v1/#{mount}/roles", { list: true })
46
+ { result: response.body }
47
+ end
48
+
49
+ def get_pki_role(role_name:, mount: 'pki', **)
50
+ response = connection(**).get("/v1/#{mount}/roles/#{role_name}")
51
+ { result: response.body }
52
+ end
53
+
54
+ def create_pki_role(role_name:, mount: 'pki', allowed_domains: nil, allow_subdomains: nil, max_ttl: nil, **)
55
+ body = { allowed_domains: allowed_domains, allow_subdomains: allow_subdomains,
56
+ max_ttl: max_ttl }.compact
57
+ response = connection(**).post("/v1/#{mount}/roles/#{role_name}", body)
58
+ { result: response.status == 204 }
59
+ end
60
+
61
+ def tidy(mount: 'pki', tidy_cert_store: true, tidy_revoked_certs: true, **)
62
+ body = { tidy_cert_store: tidy_cert_store, tidy_revoked_certs: tidy_revoked_certs }
63
+ response = connection(**).post("/v1/#{mount}/tidy", body)
64
+ { result: response.body }
65
+ end
66
+
67
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
68
+ Legion::Extensions::Helpers.const_defined?(:Lex)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Runners
9
+ module System
10
+ include Legion::Extensions::Vault::Helpers::Client
11
+
12
+ def health(**)
13
+ response = connection(**).get('/v1/sys/health')
14
+ { result: response.body }
15
+ end
16
+
17
+ def seal_status(**)
18
+ response = connection(**).get('/v1/sys/seal-status')
19
+ { result: response.body }
20
+ end
21
+
22
+ def seal(**)
23
+ response = connection(**).put('/v1/sys/seal')
24
+ { result: response.status == 204 }
25
+ end
26
+
27
+ def unseal(key:, **)
28
+ response = connection(**).put('/v1/sys/unseal', { key: key })
29
+ { result: response.body }
30
+ end
31
+
32
+ def init(secret_shares: 5, secret_threshold: 3, **)
33
+ body = { secret_shares: secret_shares, secret_threshold: secret_threshold }
34
+ response = connection(**).put('/v1/sys/init', body)
35
+ { result: response.body }
36
+ end
37
+
38
+ def list_mounts(**)
39
+ response = connection(**).get('/v1/sys/mounts')
40
+ { result: response.body }
41
+ end
42
+
43
+ def mount(path:, type:, description: nil, config: nil, **)
44
+ body = { type: type, description: description, config: config }.compact
45
+ response = connection(**).post("/v1/sys/mounts/#{path}", body)
46
+ { result: response.status == 204 }
47
+ end
48
+
49
+ def unmount(path:, **)
50
+ response = connection(**).delete("/v1/sys/mounts/#{path}")
51
+ { result: response.status == 204 }
52
+ end
53
+
54
+ def list_auth(**)
55
+ response = connection(**).get('/v1/sys/auth')
56
+ { result: response.body }
57
+ end
58
+
59
+ def enable_auth(path:, type:, description: nil, **)
60
+ body = { type: type, description: description }.compact
61
+ response = connection(**).post("/v1/sys/auth/#{path}", body)
62
+ { result: response.status == 204 }
63
+ end
64
+
65
+ def disable_auth(path:, **)
66
+ response = connection(**).delete("/v1/sys/auth/#{path}")
67
+ { result: response.status == 204 }
68
+ end
69
+
70
+ def list_policies(**)
71
+ response = connection(**).get('/v1/sys/policies/acl')
72
+ { result: response.body }
73
+ end
74
+
75
+ def get_policy(name:, **)
76
+ response = connection(**).get("/v1/sys/policies/acl/#{name}")
77
+ { result: response.body }
78
+ end
79
+
80
+ def put_policy(name:, policy:, **)
81
+ response = connection(**).put("/v1/sys/policies/acl/#{name}", { policy: policy })
82
+ { result: response.status == 204 }
83
+ end
84
+
85
+ def delete_policy(name:, **)
86
+ response = connection(**).delete("/v1/sys/policies/acl/#{name}")
87
+ { result: response.status == 204 }
88
+ end
89
+
90
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
91
+ Legion::Extensions::Helpers.const_defined?(:Lex)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Runners
9
+ module Token
10
+ include Legion::Extensions::Vault::Helpers::Client
11
+
12
+ def create_token(policies: nil, ttl: nil, renewable: nil, display_name: nil, role_name: nil, **)
13
+ body = { policies: policies, ttl: ttl, renewable: renewable, display_name: display_name }.compact
14
+ path = role_name ? "/v1/auth/token/create/#{role_name}" : '/v1/auth/token/create'
15
+ response = connection(**).post(path, body)
16
+ { result: response.body }
17
+ end
18
+
19
+ def lookup_token(token_to_lookup:, **)
20
+ response = connection(**).post('/v1/auth/token/lookup', { token: token_to_lookup })
21
+ { result: response.body }
22
+ end
23
+
24
+ def lookup_self(**)
25
+ response = connection(**).get('/v1/auth/token/lookup-self')
26
+ { result: response.body }
27
+ end
28
+
29
+ def renew_token(token_to_renew:, increment: nil, **)
30
+ body = { token: token_to_renew, increment: increment }.compact
31
+ response = connection(**).post('/v1/auth/token/renew', body)
32
+ { result: response.body }
33
+ end
34
+
35
+ def renew_self(increment: nil, **)
36
+ body = increment ? { increment: increment } : {}
37
+ response = connection(**).post('/v1/auth/token/renew-self', body)
38
+ { result: response.body }
39
+ end
40
+
41
+ def revoke_token(token_to_revoke:, **)
42
+ response = connection(**).post('/v1/auth/token/revoke', { token: token_to_revoke })
43
+ { result: response.status == 204 }
44
+ end
45
+
46
+ def revoke_self(**)
47
+ response = connection(**).post('/v1/auth/token/revoke-self')
48
+ { result: response.status == 204 }
49
+ end
50
+
51
+ def list_accessors(**)
52
+ response = connection(**).get('/v1/auth/token/accessors', { list: true })
53
+ { result: response.body }
54
+ end
55
+
56
+ def list_token_roles(**)
57
+ response = connection(**).get('/v1/auth/token/roles', { list: true })
58
+ { result: response.body }
59
+ end
60
+
61
+ def get_token_role(role_name:, **)
62
+ response = connection(**).get("/v1/auth/token/roles/#{role_name}")
63
+ { result: response.body }
64
+ end
65
+
66
+ def create_token_role(role_name:, allowed_policies: nil, orphan: nil, renewable: nil, token_ttl: nil, **)
67
+ body = { allowed_policies: allowed_policies, orphan: orphan, renewable: renewable,
68
+ token_ttl: token_ttl }.compact
69
+ response = connection(**).post("/v1/auth/token/roles/#{role_name}", body)
70
+ { result: response.status == 204 }
71
+ end
72
+
73
+ def delete_token_role(role_name:, **)
74
+ response = connection(**).delete("/v1/auth/token/roles/#{role_name}")
75
+ { result: response.status == 204 }
76
+ end
77
+
78
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
79
+ Legion::Extensions::Helpers.const_defined?(:Lex)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Vault
8
+ module Runners
9
+ module Transit
10
+ include Legion::Extensions::Vault::Helpers::Client
11
+
12
+ def create_key(name:, mount: 'transit', type: nil, **)
13
+ body = type ? { type: type } : {}
14
+ response = connection(**).post("/v1/#{mount}/keys/#{name}", body)
15
+ { result: response.status == 204 }
16
+ end
17
+
18
+ def read_key(name:, mount: 'transit', **)
19
+ response = connection(**).get("/v1/#{mount}/keys/#{name}")
20
+ { result: response.body }
21
+ end
22
+
23
+ def list_keys(mount: 'transit', **)
24
+ response = connection(**).get("/v1/#{mount}/keys", { list: true })
25
+ { result: response.body }
26
+ end
27
+
28
+ def delete_key(name:, mount: 'transit', **)
29
+ response = connection(**).delete("/v1/#{mount}/keys/#{name}")
30
+ { result: response.status == 204 }
31
+ end
32
+
33
+ def rotate_key(name:, mount: 'transit', **)
34
+ response = connection(**).post("/v1/#{mount}/keys/#{name}/rotate")
35
+ { result: response.status == 204 }
36
+ end
37
+
38
+ def encrypt(name:, plaintext:, mount: 'transit', context: nil, **)
39
+ body = { plaintext: plaintext, context: context }.compact
40
+ response = connection(**).post("/v1/#{mount}/encrypt/#{name}", body)
41
+ { result: response.body }
42
+ end
43
+
44
+ def decrypt(name:, ciphertext:, mount: 'transit', context: nil, **)
45
+ body = { ciphertext: ciphertext, context: context }.compact
46
+ response = connection(**).post("/v1/#{mount}/decrypt/#{name}", body)
47
+ { result: response.body }
48
+ end
49
+
50
+ def rewrap(name:, ciphertext:, mount: 'transit', **)
51
+ response = connection(**).post("/v1/#{mount}/rewrap/#{name}", { ciphertext: ciphertext })
52
+ { result: response.body }
53
+ end
54
+
55
+ def datakey(name:, key_type: 'plaintext', mount: 'transit', **)
56
+ response = connection(**).post("/v1/#{mount}/datakey/#{key_type}/#{name}")
57
+ { result: response.body }
58
+ end
59
+
60
+ def sign(name:, input:, mount: 'transit', hash_algorithm: nil, **)
61
+ body = { input: input, hash_algorithm: hash_algorithm }.compact
62
+ path = "/v1/#{mount}/sign/#{name}"
63
+ response = connection(**).post(path, body)
64
+ { result: response.body }
65
+ end
66
+
67
+ def verify(name:, input:, signature:, mount: 'transit', hash_algorithm: nil, **)
68
+ body = { input: input, signature: signature, hash_algorithm: hash_algorithm }.compact
69
+ path = "/v1/#{mount}/verify/#{name}"
70
+ response = connection(**).post(path, body)
71
+ { result: response.body }
72
+ end
73
+
74
+ def hash_data(input:, mount: 'transit', algorithm: 'sha2-256', **)
75
+ response = connection(**).post("/v1/#{mount}/hash/#{algorithm}", { input: input })
76
+ { result: response.body }
77
+ end
78
+
79
+ def random_bytes(bytes: 32, mount: 'transit', format: 'base64', **)
80
+ response = connection(**).post("/v1/#{mount}/random/#{bytes}", { format: format })
81
+ { result: response.body }
82
+ end
83
+
84
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
85
+ Legion::Extensions::Helpers.const_defined?(:Lex)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Vault
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/vault/version'
4
+ require 'legion/extensions/vault/helpers/client'
5
+ require 'legion/extensions/vault/runners/system'
6
+ require 'legion/extensions/vault/runners/kv'
7
+ require 'legion/extensions/vault/runners/token'
8
+ require 'legion/extensions/vault/runners/transit'
9
+ require 'legion/extensions/vault/runners/pki'
10
+ require 'legion/extensions/vault/runners/leases'
11
+ require 'legion/extensions/vault/client'
12
+
13
+ module Legion
14
+ module Extensions
15
+ module Vault
16
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-vault
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esity
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ description: Connects LegionIO to HashiCorp Vault
27
+ email:
28
+ - matthewdiverson@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".github/workflows/ci.yml"
34
+ - ".gitignore"
35
+ - ".rspec"
36
+ - ".rubocop.yml"
37
+ - CHANGELOG.md
38
+ - CLAUDE.md
39
+ - Dockerfile
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - LICENSE
43
+ - README.md
44
+ - lex-vault.gemspec
45
+ - lib/legion/extensions/vault.rb
46
+ - lib/legion/extensions/vault/client.rb
47
+ - lib/legion/extensions/vault/helpers/client.rb
48
+ - lib/legion/extensions/vault/runners/kv.rb
49
+ - lib/legion/extensions/vault/runners/leases.rb
50
+ - lib/legion/extensions/vault/runners/pki.rb
51
+ - lib/legion/extensions/vault/runners/system.rb
52
+ - lib/legion/extensions/vault/runners/token.rb
53
+ - lib/legion/extensions/vault/runners/transit.rb
54
+ - lib/legion/extensions/vault/version.rb
55
+ homepage: https://github.com/LegionIO/lex-vault
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ homepage_uri: https://github.com/LegionIO/lex-vault
60
+ source_code_uri: https://github.com/LegionIO/lex-vault
61
+ documentation_uri: https://github.com/LegionIO/lex-vault
62
+ changelog_uri: https://github.com/LegionIO/lex-vault
63
+ bug_tracker_uri: https://github.com/LegionIO/lex-vault/issues
64
+ rubygems_mfa_required: 'true'
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '3.4'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.6.9
80
+ specification_version: 4
81
+ summary: LEX Vault
82
+ test_files: []