vaultkit 1.4.0 → 1.5.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 +4 -4
- data/lib/vkit/cli/commands/agent_tokens_create_command.rb +32 -0
- data/lib/vkit/policy/bundle_compiler.rb +7 -1
- data/lib/vkit/policy/match_check.rb +7 -1
- data/lib/vkit/policy/packs/ai_safety/metadata.yaml +2 -2
- data/lib/vkit/policy/packs/ai_safety/policies/01_require_approval_agents_sensitive_prod.yaml +3 -3
- data/lib/vkit/policy/packs/ai_safety/policies/04_require_approval_agents_financial_prod.yaml +23 -0
- data/lib/vkit/policy/packs/financial_compliance/metadata.yaml +2 -2
- data/lib/vkit/policy/packs/financial_compliance/policies/01_require_approval_financial_prod.yaml +3 -2
- data/lib/vkit/policy/policy_validator.rb +54 -1
- data/lib/vkit/policy/schema/policy_bundle.schema.json +10 -2
- data/lib/vkit/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c88e22778a9f5cd606b8b8965c90c65ee9988085900491cc59e752df4fa5284
|
|
4
|
+
data.tar.gz: 0c85e8c597f0c845505a6a92002097b3a27d58dc2654cb41beb520bc9099a0c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 023b4d4d98941064f67cbd41ddf335c4d2de718b32d68bece7d26db1bcb68e587bb9dcc1b8304300636917864e6dc7ee808057db0dba431c87a1a0102421f0c3
|
|
7
|
+
data.tar.gz: 88cc76038999e9fb6c2bcd5e09130b20c1dfd0407fac44ba33c0ebaf26d7a8c721c7bdd9657ad535980b4ff12ad06b600d4bc978e849c1e6dfa2fdd399576916
|
|
@@ -43,11 +43,43 @@ module Vkit
|
|
|
43
43
|
puts
|
|
44
44
|
|
|
45
45
|
puts "⚠️ Store this token securely. It cannot be retrieved again."
|
|
46
|
+
|
|
47
|
+
warn_if_tenant_scoped(org) unless token["tenant"]
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
private
|
|
50
52
|
|
|
53
|
+
WARNING_NAMES = 5
|
|
54
|
+
|
|
55
|
+
# A token with no tenant can't read a tenant-scoped dataset at all:
|
|
56
|
+
# VaultKit answers it as if the dataset didn't exist. Say so when the
|
|
57
|
+
# org has any, so the operator isn't left guessing why.
|
|
58
|
+
def warn_if_tenant_scoped(org)
|
|
59
|
+
names = tenant_scoped_datasets(org)
|
|
60
|
+
return if names.empty?
|
|
61
|
+
|
|
62
|
+
shown = names.first(WARNING_NAMES).join(", ")
|
|
63
|
+
shown += ", and #{names.size - WARNING_NAMES} more" if names.size > WARNING_NAMES
|
|
64
|
+
count = names.size == 1 ? "1 dataset is" : "#{names.size} datasets are"
|
|
65
|
+
|
|
66
|
+
puts
|
|
67
|
+
puts "⚠️ This token has no tenant, and #{count} tenant-scoped in #{org}: #{shown}."
|
|
68
|
+
puts " VaultKit answers its requests for them as if they didn't exist (\"Dataset not found\")."
|
|
69
|
+
puts " To read them, create a token for one tenant with --tenant."
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Names of datasets with a tenant column, from the registry export.
|
|
73
|
+
# Nothing if the export can't be read: the token is already issued.
|
|
74
|
+
def tenant_scoped_datasets(org)
|
|
75
|
+
registry = authenticated_client.get("/api/v1/orgs/#{org}/registries/export")
|
|
76
|
+
return [] unless registry.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
registry.select { |_name, ds| ds.is_a?(Hash) && !ds["tenant_column"].to_s.empty? }.keys.sort
|
|
79
|
+
rescue StandardError
|
|
80
|
+
[]
|
|
81
|
+
end
|
|
82
|
+
|
|
51
83
|
def build_body(options)
|
|
52
84
|
body = {
|
|
53
85
|
name: options.fetch(:name),
|
|
@@ -121,9 +121,15 @@ module Vkit
|
|
|
121
121
|
"allow"
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
# One group stays approver_role, as older control planes read it;
|
|
125
|
+
# several become approver_roles with their approval_mode.
|
|
124
126
|
def self.extract_approval(p)
|
|
125
127
|
return unless p.dig("action", "require_approval")
|
|
126
|
-
|
|
128
|
+
|
|
129
|
+
roles = p.dig("action", "approver_roles")
|
|
130
|
+
return { "approver_role" => p.dig("action", "approver_role") } unless roles
|
|
131
|
+
|
|
132
|
+
{ "approver_roles" => roles, "approval_mode" => p.dig("action", "approval_mode") || "all" }
|
|
127
133
|
end
|
|
128
134
|
|
|
129
135
|
def self.extract_masking(p)
|
|
@@ -19,7 +19,10 @@ module Vkit
|
|
|
19
19
|
SENSITIVITIES = %w[high medium low unknown].freeze
|
|
20
20
|
|
|
21
21
|
MATCH_KEYS = %w[dataset fields context].freeze
|
|
22
|
-
FIELD_KEYS = %w[any all contains sensitivity].freeze
|
|
22
|
+
FIELD_KEYS = %w[any all contains sensitivity usage].freeze
|
|
23
|
+
# row: the query reads the field value by value (or MIN/MAX of it);
|
|
24
|
+
# aggregate: it only counts, sums, averages or groups by it.
|
|
25
|
+
USAGES = %w[row aggregate].freeze
|
|
23
26
|
CONTEXT_KEYS = %w[environment dataset_region requester_role requester_region requester_clearance time].freeze
|
|
24
27
|
ENVIRONMENTS = %w[production staging development sandbox].freeze
|
|
25
28
|
REGIONS = %w[EU US CA APAC].freeze
|
|
@@ -86,6 +89,9 @@ module Vkit
|
|
|
86
89
|
if fields.key?("sensitivity") && !@sensitivities.include?(fields["sensitivity"].to_s)
|
|
87
90
|
problems << "sensitivity #{fields['sensitivity']} never occurs. Use one of: #{@sensitivities.to_a.join(', ')}"
|
|
88
91
|
end
|
|
92
|
+
if fields.key?("usage") && !USAGES.include?(fields["usage"].to_s)
|
|
93
|
+
problems << "match.fields.usage #{fields['usage']} isn't one of: #{USAGES.join(', ')}"
|
|
94
|
+
end
|
|
89
95
|
problems
|
|
90
96
|
end
|
|
91
97
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
__pack_meta:
|
|
2
2
|
name: ai_safety
|
|
3
|
-
version: "2.
|
|
3
|
+
version: "2.1.0"
|
|
4
4
|
layer: 30
|
|
5
|
-
description: "Agents need approval for personal
|
|
5
|
+
description: "Agents need approval for personal and unreviewed data in production, for financial data read row by row in production, and for high-sensitivity data anywhere."
|
|
6
6
|
dependencies:
|
|
7
7
|
- starter
|
|
8
8
|
priority_band:
|
data/lib/vkit/policy/packs/ai_safety/policies/01_require_approval_agents_sensitive_prod.yaml
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
id: ai_require_approval_agents_sensitive_prod
|
|
2
|
-
description: Agents need security approval for personal
|
|
2
|
+
description: Agents need security approval for personal data in production. Financial data is 04_require_approval_agents_financial_prod.
|
|
3
3
|
priority: 1200
|
|
4
4
|
|
|
5
5
|
match:
|
|
6
6
|
fields:
|
|
7
|
-
contains: [pii
|
|
7
|
+
contains: [pii]
|
|
8
8
|
|
|
9
9
|
context:
|
|
10
10
|
environment: production
|
|
@@ -13,5 +13,5 @@ context:
|
|
|
13
13
|
action:
|
|
14
14
|
require_approval: true
|
|
15
15
|
approver_role: security
|
|
16
|
-
reason: Agents reading personal
|
|
16
|
+
reason: Agents reading personal data in production need security review.
|
|
17
17
|
ttl: 1h
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
id: ai_require_approval_agents_financial_prod
|
|
2
|
+
description: >-
|
|
3
|
+
Agents need a person's review to read financial data row by row in production,
|
|
4
|
+
from security or a finance manager. With financial_compliance installed, the
|
|
5
|
+
finance manager it already asks for meets this too, so one sign-off covers both.
|
|
6
|
+
Counts, sums, averages and grouping need no approval.
|
|
7
|
+
priority: 1250
|
|
8
|
+
|
|
9
|
+
match:
|
|
10
|
+
fields:
|
|
11
|
+
contains: [financial]
|
|
12
|
+
usage: row
|
|
13
|
+
|
|
14
|
+
context:
|
|
15
|
+
environment: production
|
|
16
|
+
requester_role: agent
|
|
17
|
+
|
|
18
|
+
action:
|
|
19
|
+
require_approval: true
|
|
20
|
+
approver_roles: [security, finance_manager]
|
|
21
|
+
approval_mode: any
|
|
22
|
+
reason: Agents reading financial data row by row in production need review by security or a finance manager.
|
|
23
|
+
ttl: 1h
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
__pack_meta:
|
|
2
2
|
name: financial_compliance
|
|
3
|
-
version: "2.
|
|
3
|
+
version: "2.1.0"
|
|
4
4
|
layer: 40
|
|
5
|
-
description: "Finance approval for financial data in production, and short-lived grants for financial fields."
|
|
5
|
+
description: "Finance approval for reading financial data row by row in production (totals and averages need none), and short-lived grants for financial fields."
|
|
6
6
|
dependencies:
|
|
7
7
|
- starter
|
|
8
8
|
priority_band:
|
data/lib/vkit/policy/packs/financial_compliance/policies/01_require_approval_financial_prod.yaml
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
id: fin_require_approval_financial_prod
|
|
2
|
-
description: A finance manager approves
|
|
2
|
+
description: A finance manager approves reading financial data row by row in production. Counts, sums, averages and grouping need no approval; MIN and MAX return a single value, so they do.
|
|
3
3
|
priority: 2100
|
|
4
4
|
|
|
5
5
|
match:
|
|
6
6
|
fields:
|
|
7
7
|
contains: [financial]
|
|
8
|
+
usage: row
|
|
8
9
|
|
|
9
10
|
context:
|
|
10
11
|
environment: production
|
|
@@ -12,5 +13,5 @@ context:
|
|
|
12
13
|
action:
|
|
13
14
|
require_approval: true
|
|
14
15
|
approver_role: finance_manager
|
|
15
|
-
reason:
|
|
16
|
+
reason: Reading financial data row by row in production needs finance approval.
|
|
16
17
|
ttl: 30m
|
|
@@ -80,7 +80,7 @@ module Vkit
|
|
|
80
80
|
|
|
81
81
|
when "require_approval"
|
|
82
82
|
require_reason!(action, prefix)
|
|
83
|
-
|
|
83
|
+
validate_approvers!(action, prefix)
|
|
84
84
|
|
|
85
85
|
when "mask"
|
|
86
86
|
# masking is optional, but if present must be valid
|
|
@@ -93,6 +93,59 @@ module Vkit
|
|
|
93
93
|
validate_ttl!(action["ttl"], prefix) if action.key?("ttl")
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
APPROVAL_MODES = %w[all any].freeze
|
|
97
|
+
|
|
98
|
+
# One group (approver_role), or several (approver_roles) with
|
|
99
|
+
# approval_mode: all (the default, every group signs off) or any (one
|
|
100
|
+
# of them does).
|
|
101
|
+
def self.validate_approvers!(action, prefix)
|
|
102
|
+
single = action["approver_role"]
|
|
103
|
+
several = action["approver_roles"]
|
|
104
|
+
|
|
105
|
+
if single && several
|
|
106
|
+
raise ValidationError, <<~MSG
|
|
107
|
+
#{prefix}Set approver_role or approver_roles, not both.
|
|
108
|
+
MSG
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if several.nil?
|
|
112
|
+
require_string!(action, "approver_role", prefix)
|
|
113
|
+
if action.key?("approval_mode")
|
|
114
|
+
raise ValidationError, <<~MSG
|
|
115
|
+
#{prefix}approval_mode only applies to approver_roles (several groups).
|
|
116
|
+
MSG
|
|
117
|
+
end
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
unless several.is_a?(Array) && several.any? && several.all? { |r| r.is_a?(String) && !r.strip.empty? }
|
|
122
|
+
raise ValidationError, <<~MSG
|
|
123
|
+
#{prefix}approver_roles must be a list of approver group names.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
action:
|
|
128
|
+
require_approval: true
|
|
129
|
+
approver_roles: [security, data_owner]
|
|
130
|
+
approval_mode: any
|
|
131
|
+
MSG
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if several.uniq.size != several.size
|
|
135
|
+
raise ValidationError, "#{prefix}approver_roles lists a group more than once.\n"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
mode = action.fetch("approval_mode", "all")
|
|
139
|
+
return if APPROVAL_MODES.include?(mode.to_s)
|
|
140
|
+
|
|
141
|
+
raise ValidationError, <<~MSG
|
|
142
|
+
#{prefix}approval_mode must be all (every group signs off) or any (one of them does).
|
|
143
|
+
|
|
144
|
+
Got:
|
|
145
|
+
approval_mode: #{mode.inspect}
|
|
146
|
+
MSG
|
|
147
|
+
end
|
|
148
|
+
|
|
96
149
|
def self.require_reason!(action, prefix)
|
|
97
150
|
reason = action["reason"]
|
|
98
151
|
return if reason.is_a?(String) && !reason.strip.empty?
|
|
@@ -214,7 +214,8 @@
|
|
|
214
214
|
"contains": {
|
|
215
215
|
"type": "array",
|
|
216
216
|
"items": { "type": "string" }
|
|
217
|
-
}
|
|
217
|
+
},
|
|
218
|
+
"usage": { "type": "string", "enum": ["row", "aggregate"] }
|
|
218
219
|
}
|
|
219
220
|
},
|
|
220
221
|
"context": {
|
|
@@ -293,7 +294,14 @@
|
|
|
293
294
|
"type": "object",
|
|
294
295
|
"additionalProperties": false,
|
|
295
296
|
"properties": {
|
|
296
|
-
"approver_role": { "type": "string" }
|
|
297
|
+
"approver_role": { "type": "string" },
|
|
298
|
+
"approver_roles": {
|
|
299
|
+
"type": "array",
|
|
300
|
+
"minItems": 1,
|
|
301
|
+
"uniqueItems": true,
|
|
302
|
+
"items": { "type": "string", "minLength": 1 }
|
|
303
|
+
},
|
|
304
|
+
"approval_mode": { "type": "string", "enum": ["all", "any"] }
|
|
297
305
|
}
|
|
298
306
|
},
|
|
299
307
|
|
data/lib/vkit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vaultkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nnamdi Ogundu
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-11-
|
|
11
|
+
date: 2026-11-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|
|
@@ -101,6 +101,7 @@ files:
|
|
|
101
101
|
- lib/vkit/policy/packs/ai_safety/policies/01_require_approval_agents_sensitive_prod.yaml
|
|
102
102
|
- lib/vkit/policy/packs/ai_safety/policies/02_require_approval_agents_high_sensitivity.yaml
|
|
103
103
|
- lib/vkit/policy/packs/ai_safety/policies/03_require_approval_agents_unknown_prod.yaml
|
|
104
|
+
- lib/vkit/policy/packs/ai_safety/policies/04_require_approval_agents_financial_prod.yaml
|
|
104
105
|
- lib/vkit/policy/packs/financial_compliance/metadata.yaml
|
|
105
106
|
- lib/vkit/policy/packs/financial_compliance/policies/01_require_approval_financial_prod.yaml
|
|
106
107
|
- lib/vkit/policy/packs/financial_compliance/policies/02_short_ttl_for_financial_grants.yaml
|