lex-identity-kubernetes 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 +7 -0
- data/.github/CODEOWNERS +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/ci.yml +34 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +18 -0
- data/CLAUDE.md +78 -0
- data/Gemfile +11 -0
- data/README.md +77 -0
- data/lex-identity-kubernetes.gemspec +32 -0
- data/lib/legion/extensions/identity/kubernetes/identity.rb +173 -0
- data/lib/legion/extensions/identity/kubernetes/settings.rb +33 -0
- data/lib/legion/extensions/identity/kubernetes/version.rb +11 -0
- data/lib/legion/extensions/identity/kubernetes.rb +35 -0
- metadata +103 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 64532b0d40a6355aae79ca7e617fe31c4eeac3ab314cc19e332ae08a82c1a81a
|
|
4
|
+
data.tar.gz: 87bbcc620b450a6164831b4c12b3fd1c244d3ebae039444cd369ad91e3b35f23
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 16d652cd69c68996700401b73979b2624d44a4d24a3ba6215f7a9a266e36710443c8a962db595580fd1b0cf19d0497bbdb8578a595d6be152c4bc3d24690175b
|
|
7
|
+
data.tar.gz: c79c166fa01b11844c6c91507eab494b03abc4c831eb41908adc82920f515cbc777d564d8bd4b3fb8fa922cb9bc199b984661726c8a64e8510d732988b36e483
|
data/.github/CODEOWNERS
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: bundler
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: monday
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
labels:
|
|
10
|
+
- "type:dependencies"
|
|
11
|
+
- package-ecosystem: github-actions
|
|
12
|
+
directory: /
|
|
13
|
+
schedule:
|
|
14
|
+
interval: weekly
|
|
15
|
+
day: monday
|
|
16
|
+
open-pull-requests-limit: 5
|
|
17
|
+
labels:
|
|
18
|
+
- "type:dependencies"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: '0 9 * * 1'
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
ci:
|
|
11
|
+
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
12
|
+
|
|
13
|
+
excluded-files:
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
|
|
15
|
+
|
|
16
|
+
security:
|
|
17
|
+
uses: LegionIO/.github/.github/workflows/security-scan.yml@main
|
|
18
|
+
|
|
19
|
+
version-changelog:
|
|
20
|
+
uses: LegionIO/.github/.github/workflows/version-changelog.yml@main
|
|
21
|
+
|
|
22
|
+
dependency-review:
|
|
23
|
+
uses: LegionIO/.github/.github/workflows/dependency-review.yml@main
|
|
24
|
+
|
|
25
|
+
stale:
|
|
26
|
+
if: github.event_name == 'schedule'
|
|
27
|
+
uses: LegionIO/.github/.github/workflows/stale.yml@main
|
|
28
|
+
|
|
29
|
+
release:
|
|
30
|
+
needs: [ci, excluded-files]
|
|
31
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
32
|
+
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
33
|
+
secrets:
|
|
34
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release
|
|
8
|
+
- `Identity` module implementing the LegionIO identity provider contract
|
|
9
|
+
- `provider_type: :auth`, `facing: :machine`, `priority: 95`, `trust_weight: 100`
|
|
10
|
+
- `capabilities: [:authenticate, :vault_auth]`
|
|
11
|
+
- `resolve` reads projected SA token from `/var/run/secrets/kubernetes.io/serviceaccount/token`
|
|
12
|
+
- Vault-verified path: when `Legion::Crypt::LeaseManager` is available, validates token via `auth/kubernetes/login` and populates groups from Vault policies
|
|
13
|
+
- Unverified JWT fallback: decodes JWT payload without signature verification; `groups` always `[]` on this path (RBAC safety); logs warning
|
|
14
|
+
- `provide_token` returns a `Legion::Identity::Lease` with `renewable: true` (projected tokens auto-rotate)
|
|
15
|
+
- `vault_auth` delegates to `LeaseManager#vault_logical` for namespace-aware Vault calls
|
|
16
|
+
- `canonical_name` derived from `namespace-sa_name` (dots and special chars replaced with hyphens)
|
|
17
|
+
- `Settings` module with configurable `token_path`, `namespace_path`, `vault_role`, `vault_auth_path`
|
|
18
|
+
- `normalize` strips non-alphanumeric characters, collapses consecutive hyphens, trims leading/trailing hyphens
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# lex-identity-kubernetes: Kubernetes Service Account Identity Provider
|
|
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
|
+
Kubernetes service account identity provider for LegionIO. Reads projected SA tokens from the standard K8s mount path and validates them via Vault Kubernetes auth (preferred) or falls back to unverified JWT decode.
|
|
10
|
+
|
|
11
|
+
**GitHub**: https://github.com/LegionIO/lex-identity-kubernetes
|
|
12
|
+
**License**: MIT
|
|
13
|
+
**Version**: 0.1.0
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Legion::Extensions::Identity::Kubernetes
|
|
19
|
+
├── Identity # provider contract: resolve, provide_token, vault_auth, normalize, ...
|
|
20
|
+
└── Settings # default settings (token_path, namespace_path, vault_role, vault_auth_path)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
No runners, no actors, no transport. Identity-only gem.
|
|
24
|
+
|
|
25
|
+
## File Map
|
|
26
|
+
|
|
27
|
+
| File | Purpose |
|
|
28
|
+
|------|---------|
|
|
29
|
+
| `lib/legion/extensions/identity/kubernetes.rb` | Entry point — requires version/settings/identity, declares `identity_provider?`, `remote_invocable?`, top-level delegation methods |
|
|
30
|
+
| `lib/legion/extensions/identity/kubernetes/identity.rb` | Provider contract: `resolve`, `provide_token`, `vault_auth`, `normalize`, private helpers |
|
|
31
|
+
| `lib/legion/extensions/identity/kubernetes/settings.rb` | Default settings, `Settings.get` merges with `Legion::Settings` |
|
|
32
|
+
| `lib/legion/extensions/identity/kubernetes/version.rb` | `VERSION = '0.1.0'` |
|
|
33
|
+
|
|
34
|
+
## Provider Contract
|
|
35
|
+
|
|
36
|
+
| Method | Return |
|
|
37
|
+
|--------|--------|
|
|
38
|
+
| `provider_name` | `:kubernetes` |
|
|
39
|
+
| `provider_type` | `:auth` |
|
|
40
|
+
| `facing` | `:machine` |
|
|
41
|
+
| `priority` | `95` |
|
|
42
|
+
| `trust_weight` | `100` |
|
|
43
|
+
| `capabilities` | `[:authenticate, :vault_auth]` |
|
|
44
|
+
| `resolve` | identity hash or `nil` |
|
|
45
|
+
| `provide_token` | `Legion::Identity::Lease` or `nil` |
|
|
46
|
+
| `vault_auth(token:)` | Vault response or `nil` |
|
|
47
|
+
| `normalize(val)` | String |
|
|
48
|
+
|
|
49
|
+
## Key Design Rules
|
|
50
|
+
|
|
51
|
+
- `groups: []` on unverified path — NEVER populate groups from unverified JWT for RBAC safety
|
|
52
|
+
- Log warning when using unverified path: `"K8s identity resolved without cryptographic verification — groups empty"`
|
|
53
|
+
- `groups` from Vault-verified path only — populated from `response.auth.policies`
|
|
54
|
+
- `provide_token` re-reads SA token from file each call — projected tokens auto-rotate
|
|
55
|
+
- `renewable: true` — signals `LeaseRenewer` to call `provide_token` periodically
|
|
56
|
+
- `canonical_name` = `normalize("#{namespace}-#{sa_name}")` — dots replaced with hyphens
|
|
57
|
+
- `private_class_method` explicit method names for all private helpers
|
|
58
|
+
- `vault_logical` via `LeaseManager.instance.vault_logical` (NOT raw `::Vault.logical`)
|
|
59
|
+
- `Legion::JSON.load` returns SYMBOL keys — use `:sub`, `:exp`, `:groups`, NOT string keys
|
|
60
|
+
|
|
61
|
+
## Dependencies
|
|
62
|
+
|
|
63
|
+
| Gem | Purpose |
|
|
64
|
+
|-----|---------|
|
|
65
|
+
| `legion-json` | JSON serialization (`symbolize_names: true` — SYMBOL keys) |
|
|
66
|
+
| `legion-settings` | Configuration management |
|
|
67
|
+
|
|
68
|
+
## Testing
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bundle install
|
|
72
|
+
bundle exec rspec
|
|
73
|
+
bundle exec rubocop
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
**Maintained By**: Matthew Iverson (@Esity)
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# lex-identity-kubernetes
|
|
2
|
+
|
|
3
|
+
Kubernetes service account identity provider for LegionIO. Reads projected SA tokens from the standard mount path and optionally validates them via Vault Kubernetes auth.
|
|
4
|
+
|
|
5
|
+
**Gem**: `lex-identity-kubernetes`
|
|
6
|
+
**Version**: 0.1.0
|
|
7
|
+
**License**: MIT
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
When running inside a Kubernetes pod, LegionIO can establish machine identity from the pod's service account. This provider:
|
|
12
|
+
|
|
13
|
+
1. Reads the projected SA token from `/var/run/secrets/kubernetes.io/serviceaccount/token`
|
|
14
|
+
2. If Vault is connected: validates the token via `auth/kubernetes/login` and derives groups from Vault policies (verified path)
|
|
15
|
+
3. If Vault is not connected: decodes the JWT payload without signature verification (unverified fallback) — groups are always empty on this path for RBAC safety
|
|
16
|
+
|
|
17
|
+
## Identity Shape
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
{
|
|
21
|
+
canonical_name: 'legionio-legion-worker', # namespace-sa_name
|
|
22
|
+
kind: :machine,
|
|
23
|
+
source: :kubernetes,
|
|
24
|
+
persistent: true,
|
|
25
|
+
groups: ['default', 'legionio'], # from Vault policies (verified only)
|
|
26
|
+
metadata: {
|
|
27
|
+
namespace: 'legionio',
|
|
28
|
+
service_account: 'legion-worker',
|
|
29
|
+
verified: true # false on unverified path
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Settings at `Legion::Settings[:identity][:kubernetes]`:
|
|
37
|
+
|
|
38
|
+
| Key | Default | Description |
|
|
39
|
+
|-----|---------|-------------|
|
|
40
|
+
| `token_path` | `/var/run/secrets/kubernetes.io/serviceaccount/token` | SA token file path |
|
|
41
|
+
| `namespace_path` | `/var/run/secrets/kubernetes.io/serviceaccount/namespace` | Namespace file path |
|
|
42
|
+
| `vault_role` | `legionio` | Vault Kubernetes auth role |
|
|
43
|
+
| `vault_auth_path` | `kubernetes` | Vault auth mount path |
|
|
44
|
+
|
|
45
|
+
## Provider Contract
|
|
46
|
+
|
|
47
|
+
| Method | Return |
|
|
48
|
+
|--------|--------|
|
|
49
|
+
| `provider_name` | `:kubernetes` |
|
|
50
|
+
| `provider_type` | `:auth` |
|
|
51
|
+
| `facing` | `:machine` |
|
|
52
|
+
| `priority` | `95` |
|
|
53
|
+
| `trust_weight` | `100` |
|
|
54
|
+
| `capabilities` | `[:authenticate, :vault_auth]` |
|
|
55
|
+
|
|
56
|
+
## Vault Setup
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
vault auth enable kubernetes
|
|
60
|
+
vault write auth/kubernetes/config \
|
|
61
|
+
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
|
62
|
+
kubernetes_host="https://kubernetes.default.svc"
|
|
63
|
+
|
|
64
|
+
vault write auth/kubernetes/role/legionio \
|
|
65
|
+
bound_service_account_names=legion-worker \
|
|
66
|
+
bound_service_account_namespaces=legionio \
|
|
67
|
+
policies=default,legionio \
|
|
68
|
+
ttl=1h
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bundle install
|
|
75
|
+
bundle exec rspec
|
|
76
|
+
bundle exec rubocop
|
|
77
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/identity/kubernetes/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-identity-kubernetes'
|
|
7
|
+
spec.version = Legion::Extensions::Identity::Kubernetes::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Identity Kubernetes'
|
|
12
|
+
spec.description = 'LegionIO Kubernetes service account identity provider — reads projected SA tokens and optionally validates via Vault Kubernetes auth'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-identity-kubernetes'
|
|
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-identity-kubernetes'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-identity-kubernetes'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-identity-kubernetes'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-identity-kubernetes/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 'base64', '>= 0.2'
|
|
30
|
+
spec.add_dependency 'legion-json', '>= 1.2.1'
|
|
31
|
+
spec.add_dependency 'legion-settings', '>= 1.3.14'
|
|
32
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Identity
|
|
8
|
+
module Kubernetes
|
|
9
|
+
module Identity
|
|
10
|
+
SA_TOKEN_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/token'
|
|
11
|
+
SA_NAMESPACE_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/namespace'
|
|
12
|
+
|
|
13
|
+
def self.provider_name
|
|
14
|
+
:kubernetes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.provider_type
|
|
18
|
+
:auth
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.facing
|
|
22
|
+
:machine
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.priority
|
|
26
|
+
95
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.trust_weight
|
|
30
|
+
100
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.capabilities
|
|
34
|
+
%i[authenticate vault_auth]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.resolve(canonical_name: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
38
|
+
token = read_sa_token
|
|
39
|
+
return nil unless token
|
|
40
|
+
|
|
41
|
+
if vault_available?
|
|
42
|
+
vault_resolve(token)
|
|
43
|
+
else
|
|
44
|
+
unverified_resolve(token)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.provide_token
|
|
49
|
+
token = read_sa_token
|
|
50
|
+
return nil unless token
|
|
51
|
+
|
|
52
|
+
claims = decode_jwt_claims(token)
|
|
53
|
+
expires_at = claims && claims[:exp] ? Time.at(claims[:exp]) : nil
|
|
54
|
+
|
|
55
|
+
Legion::Identity::Lease.new(
|
|
56
|
+
provider: :kubernetes,
|
|
57
|
+
credential: token,
|
|
58
|
+
expires_at: expires_at,
|
|
59
|
+
renewable: true,
|
|
60
|
+
issued_at: Time.now,
|
|
61
|
+
metadata: { namespace: read_namespace }
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.vault_auth(token: nil)
|
|
66
|
+
token ||= read_sa_token
|
|
67
|
+
return nil unless token
|
|
68
|
+
|
|
69
|
+
logical = if defined?(Legion::Crypt::LeaseManager)
|
|
70
|
+
Legion::Crypt::LeaseManager.instance.vault_logical
|
|
71
|
+
elsif defined?(::Vault)
|
|
72
|
+
::Vault.logical
|
|
73
|
+
end
|
|
74
|
+
return nil unless logical
|
|
75
|
+
|
|
76
|
+
logical.write(
|
|
77
|
+
"auth/#{settings[:vault_auth_path] || 'kubernetes'}/login",
|
|
78
|
+
role: settings[:vault_role] || 'legionio',
|
|
79
|
+
jwt: token
|
|
80
|
+
)
|
|
81
|
+
rescue StandardError => e
|
|
82
|
+
Legion::Logging.warn("K8s Vault auth failed: #{e.message}") if defined?(Legion::Logging) # rubocop:disable Legion/HelperMigration/DirectLogging,Legion/HelperMigration/LoggingGuard
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.normalize(val)
|
|
87
|
+
val.to_s.downcase.strip.gsub(/[^a-z0-9_-]/, '-').gsub(/-{2,}/, '-').gsub(/^-|-$/, '')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.settings
|
|
91
|
+
Kubernetes::Settings.get
|
|
92
|
+
end
|
|
93
|
+
private_class_method :settings
|
|
94
|
+
|
|
95
|
+
def self.vault_available?
|
|
96
|
+
defined?(Legion::Crypt::LeaseManager) &&
|
|
97
|
+
Legion::Crypt::LeaseManager.instance.respond_to?(:vault_logical)
|
|
98
|
+
end
|
|
99
|
+
private_class_method :vault_available?
|
|
100
|
+
|
|
101
|
+
def self.vault_resolve(token)
|
|
102
|
+
response = vault_auth(token: token)
|
|
103
|
+
return unverified_resolve(token) unless response
|
|
104
|
+
|
|
105
|
+
policies = response.auth&.policies || []
|
|
106
|
+
metadata = response.auth&.metadata || {}
|
|
107
|
+
namespace = metadata[:kubernetes_namespace] || read_namespace
|
|
108
|
+
sa_name = metadata[:kubernetes_service_account] || 'unknown'
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
canonical_name: normalize("#{namespace}-#{sa_name}"),
|
|
112
|
+
kind: :machine,
|
|
113
|
+
source: :kubernetes,
|
|
114
|
+
persistent: true,
|
|
115
|
+
groups: policies,
|
|
116
|
+
metadata: { namespace: namespace, service_account: sa_name, verified: true }
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
private_class_method :vault_resolve
|
|
120
|
+
|
|
121
|
+
def self.unverified_resolve(token)
|
|
122
|
+
claims = decode_jwt_claims(token)
|
|
123
|
+
return nil unless claims
|
|
124
|
+
|
|
125
|
+
Legion::Logging.warn('K8s identity resolved without cryptographic verification — groups empty') if defined?(Legion::Logging) # rubocop:disable Legion/HelperMigration/DirectLogging,Legion/HelperMigration/LoggingGuard
|
|
126
|
+
|
|
127
|
+
parts = claims[:sub].to_s.split(':')
|
|
128
|
+
namespace = parts[2] || read_namespace
|
|
129
|
+
sa_name = parts[3] || 'unknown'
|
|
130
|
+
|
|
131
|
+
{
|
|
132
|
+
canonical_name: normalize("#{namespace}-#{sa_name}"),
|
|
133
|
+
kind: :machine,
|
|
134
|
+
source: :kubernetes,
|
|
135
|
+
persistent: true,
|
|
136
|
+
groups: [],
|
|
137
|
+
metadata: { namespace: namespace, service_account: sa_name, verified: false }
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
private_class_method :unverified_resolve
|
|
141
|
+
|
|
142
|
+
def self.read_sa_token
|
|
143
|
+
path = settings[:token_path] || SA_TOKEN_PATH
|
|
144
|
+
return nil unless File.exist?(path)
|
|
145
|
+
|
|
146
|
+
File.read(path).strip
|
|
147
|
+
end
|
|
148
|
+
private_class_method :read_sa_token
|
|
149
|
+
|
|
150
|
+
def self.read_namespace
|
|
151
|
+
path = settings[:namespace_path] || SA_NAMESPACE_PATH
|
|
152
|
+
return nil unless File.exist?(path)
|
|
153
|
+
|
|
154
|
+
File.read(path).strip
|
|
155
|
+
end
|
|
156
|
+
private_class_method :read_namespace
|
|
157
|
+
|
|
158
|
+
def self.decode_jwt_claims(token)
|
|
159
|
+
payload = token.split('.')[1]
|
|
160
|
+
return nil unless payload
|
|
161
|
+
|
|
162
|
+
padded = payload + ('=' * ((4 - (payload.length % 4)) % 4))
|
|
163
|
+
Legion::JSON.load(Base64.urlsafe_decode64(padded)) # rubocop:disable Legion/HelperMigration/DirectJson
|
|
164
|
+
rescue StandardError => e
|
|
165
|
+
Legion::Logging.debug("K8s JWT decode failed: #{e.message}") if defined?(Legion::Logging) # rubocop:disable Legion/HelperMigration/DirectLogging,Legion/HelperMigration/LoggingGuard
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
private_class_method :decode_jwt_claims
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Identity
|
|
6
|
+
module Kubernetes
|
|
7
|
+
module Settings
|
|
8
|
+
DEFAULTS = {
|
|
9
|
+
token_path: '/var/run/secrets/kubernetes.io/serviceaccount/token',
|
|
10
|
+
namespace_path: '/var/run/secrets/kubernetes.io/serviceaccount/namespace',
|
|
11
|
+
vault_role: 'legionio',
|
|
12
|
+
vault_auth_path: 'kubernetes'
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def self.load
|
|
16
|
+
return unless defined?(Legion::Settings)
|
|
17
|
+
|
|
18
|
+
Legion::Settings.merge_settings(:kubernetes, DEFAULTS)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.get
|
|
22
|
+
return DEFAULTS unless defined?(Legion::Settings)
|
|
23
|
+
|
|
24
|
+
settings = Legion::Settings.dig(:identity, :kubernetes)
|
|
25
|
+
return DEFAULTS if settings.nil?
|
|
26
|
+
|
|
27
|
+
DEFAULTS.merge(settings)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/identity/kubernetes/version'
|
|
4
|
+
require 'legion/extensions/identity/kubernetes/settings'
|
|
5
|
+
require 'legion/extensions/identity/kubernetes/identity'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Identity
|
|
10
|
+
module Kubernetes
|
|
11
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined?(:Core, false)
|
|
12
|
+
|
|
13
|
+
def self.identity_provider?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.remote_invocable?
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.provider_name
|
|
22
|
+
Identity.provider_name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.provider_type
|
|
26
|
+
Identity.provider_type
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.facing
|
|
30
|
+
Identity.facing
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-identity-kubernetes
|
|
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: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: legion-json
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.2.1
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.2.1
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: legion-settings
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 1.3.14
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 1.3.14
|
|
54
|
+
description: LegionIO Kubernetes service account identity provider — reads projected
|
|
55
|
+
SA tokens and optionally validates via Vault Kubernetes auth
|
|
56
|
+
email:
|
|
57
|
+
- matthewdiverson@gmail.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".github/CODEOWNERS"
|
|
63
|
+
- ".github/dependabot.yml"
|
|
64
|
+
- ".github/workflows/ci.yml"
|
|
65
|
+
- ".gitignore"
|
|
66
|
+
- ".rubocop.yml"
|
|
67
|
+
- CHANGELOG.md
|
|
68
|
+
- CLAUDE.md
|
|
69
|
+
- Gemfile
|
|
70
|
+
- README.md
|
|
71
|
+
- lex-identity-kubernetes.gemspec
|
|
72
|
+
- lib/legion/extensions/identity/kubernetes.rb
|
|
73
|
+
- lib/legion/extensions/identity/kubernetes/identity.rb
|
|
74
|
+
- lib/legion/extensions/identity/kubernetes/settings.rb
|
|
75
|
+
- lib/legion/extensions/identity/kubernetes/version.rb
|
|
76
|
+
homepage: https://github.com/LegionIO/lex-identity-kubernetes
|
|
77
|
+
licenses:
|
|
78
|
+
- MIT
|
|
79
|
+
metadata:
|
|
80
|
+
homepage_uri: https://github.com/LegionIO/lex-identity-kubernetes
|
|
81
|
+
source_code_uri: https://github.com/LegionIO/lex-identity-kubernetes
|
|
82
|
+
documentation_uri: https://github.com/LegionIO/lex-identity-kubernetes
|
|
83
|
+
changelog_uri: https://github.com/LegionIO/lex-identity-kubernetes
|
|
84
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-identity-kubernetes/issues
|
|
85
|
+
rubygems_mfa_required: 'true'
|
|
86
|
+
rdoc_options: []
|
|
87
|
+
require_paths:
|
|
88
|
+
- lib
|
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '3.4'
|
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: '0'
|
|
99
|
+
requirements: []
|
|
100
|
+
rubygems_version: 3.6.9
|
|
101
|
+
specification_version: 4
|
|
102
|
+
summary: LEX Identity Kubernetes
|
|
103
|
+
test_files: []
|