cs-image-system-posix-identity-plugin 0.1.1__tar.gz
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.
- cs_image_system_posix_identity_plugin-0.1.1/.gitignore +26 -0
- cs_image_system_posix_identity_plugin-0.1.1/LICENSE +202 -0
- cs_image_system_posix_identity_plugin-0.1.1/PKG-INFO +225 -0
- cs_image_system_posix_identity_plugin-0.1.1/README.md +210 -0
- cs_image_system_posix_identity_plugin-0.1.1/pyproject.toml +33 -0
- cs_image_system_posix_identity_plugin-0.1.1/src/cs_image_system/posix_identity_plugin/__init__.py +3 -0
- cs_image_system_posix_identity_plugin-0.1.1/src/cs_image_system/posix_identity_plugin/accounts.py +251 -0
- cs_image_system_posix_identity_plugin-0.1.1/src/cs_image_system/posix_identity_plugin/main.py +26 -0
- cs_image_system_posix_identity_plugin-0.1.1/src/cs_image_system/posix_identity_plugin/posix_builders.py +241 -0
- cs_image_system_posix_identity_plugin-0.1.1/src/cs_image_system/posix_identity_plugin/posix_models.py +49 -0
- cs_image_system_posix_identity_plugin-0.1.1/src/cs_image_system/posix_identity_plugin/ssh_login.py +90 -0
- cs_image_system_posix_identity_plugin-0.1.1/tests/test_posix_accounts.py +108 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.envrc
|
|
2
|
+
# Okta API-integration key material -- never commit
|
|
3
|
+
.private_key.pem
|
|
4
|
+
.private_key.json
|
|
5
|
+
.public_key.json
|
|
6
|
+
*.pem
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
.venv/
|
|
14
|
+
.DS_Store
|
|
15
|
+
packages/**/*.code-workspace
|
|
16
|
+
trial
|
|
17
|
+
tfmodules/tftest
|
|
18
|
+
YAML_DUMP.yaml
|
|
19
|
+
.obsidian
|
|
20
|
+
.tofu-plugin-cache/
|
|
21
|
+
_uncommitted/
|
|
22
|
+
target/
|
|
23
|
+
# never publishable, whatever the root (stage 35 refuses them; stage 40's publish-tree checks the policy)
|
|
24
|
+
tfplan
|
|
25
|
+
*.tfstate
|
|
26
|
+
*.tfstate.backup
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cs-image-system-posix-identity-plugin
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The posix identity plugin: groups, users, memberships, keys and sudo as POSIX accounts on the machines, with ids the configuration declares; alone, or beside Okta for groups
|
|
5
|
+
Project-URL: Repository, https://github.com/infrastructurebuilder/cs-image-system-3
|
|
6
|
+
Project-URL: Documentation, https://github.com/infrastructurebuilder/cs-image-system-3/blob/develop/docs/OPERATIONS.md
|
|
7
|
+
Project-URL: Package README, https://github.com/infrastructurebuilder/cs-image-system-3/blob/develop/packages/posix-identity-plugin/README.md
|
|
8
|
+
Author-email: Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Requires-Dist: cs-image-system-system==0.1.1
|
|
13
|
+
Requires-Dist: pydantic>=2.13
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<!--
|
|
17
|
+
SPDX-FileCopyrightText: 2026 Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
18
|
+
|
|
19
|
+
SPDX-License-Identifier: Apache-2.0
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
# cs-image-system-posix-identity-plugin
|
|
23
|
+
|
|
24
|
+
The posix identity plugin: groups and users that exist as **POSIX accounts on
|
|
25
|
+
the machines**, with ids the configuration declares. It registers a group
|
|
26
|
+
builder and a user builder, both `type: posix`, under the identity type
|
|
27
|
+
`posix`. Neither has a terraform root or asks a directory: the configuration
|
|
28
|
+
is the authority, and what it describes is written onto the machines.
|
|
29
|
+
|
|
30
|
+
Why it exists (stage 75): on a machine of an Okta-managed group, OPA's agent
|
|
31
|
+
creates each person's account and user-private group, but **never the group
|
|
32
|
+
that owns the machine** -- `getent group coops` on `coops-model-005` found
|
|
33
|
+
nothing, so the group's `2770` storage subtree was owned by a bare number no
|
|
34
|
+
account belonged to. This plugin makes that group exist, with the right id.
|
|
35
|
+
|
|
36
|
+
What it does, and when:
|
|
37
|
+
|
|
38
|
+
- **At the bake of an instance image** owned by a posix group, the group is
|
|
39
|
+
created with its declared `gid:` -- or adopted when it already stands with
|
|
40
|
+
that gid, or the bake stops when it stands with another (a name known to
|
|
41
|
+
two sides with two ids is a configuration error, the operator's rule).
|
|
42
|
+
- **A base image** that declares `identity_types: [posix]` checks that the
|
|
43
|
+
shadow tools and sudo are there (`groupadd`, `useradd`, `gpasswd`,
|
|
44
|
+
`visudo`); it installs nothing, since every supported family ships them.
|
|
45
|
+
- **Generated IaC** writes a posix group's gid as the number: there is no
|
|
46
|
+
identity root to read it from, so the storage and instance roots declare
|
|
47
|
+
no remote state for it.
|
|
48
|
+
- **`validate`** resolves every POSIX id the configuration claims (see
|
|
49
|
+
"Configuration reference").
|
|
50
|
+
|
|
51
|
+
- **After every applying instance run**, each launched, RUNNING machine of
|
|
52
|
+
a posix group gets the group's accounts script over the runtime's session
|
|
53
|
+
(SSM on AWS, the guest session on GCE), run as root: every member and
|
|
54
|
+
admin as an account (its `uid:`, a user-private group of the same name
|
|
55
|
+
and id, a home, exactly its `public_keys:` as `authorized_keys`), the
|
|
56
|
+
members (admins among them) as the group's members, and NOPASSWD sudo
|
|
57
|
+
for the admins unless `admin_sudo: false`. The script is
|
|
58
|
+
[`accounts.py`](src/cs_image_system/posix_identity_plugin/accounts.py)'s;
|
|
59
|
+
it is idempotent, adopts what stands equal and refuses what stands
|
|
60
|
+
different. It runs after launch rather than in the launch script on
|
|
61
|
+
purpose: the launch script is the machine's immutable snapshot, so a
|
|
62
|
+
membership change there would mean a new machine; here it means the next
|
|
63
|
+
applying run. A machine that is off waits for a run that finds it
|
|
64
|
+
running; a machine that booted this run is waited for (bounded).
|
|
65
|
+
A failure is logged as an error naming the machine and never stops the
|
|
66
|
+
run; the next applying run tries again.
|
|
67
|
+
|
|
68
|
+
## The login proof
|
|
69
|
+
|
|
70
|
+
`verify login` (CI's perform job runs it) logs into each standing machine
|
|
71
|
+
of a posix group as the group's **proof user** and checks it is in the
|
|
72
|
+
group -- access proved, not only health:
|
|
73
|
+
|
|
74
|
+
- **The proof user** is a member or admin declared on a posix user
|
|
75
|
+
builder with `is_service_account: true`, a `uid:` and its public key in
|
|
76
|
+
`public_keys:` (the first such member by name). A group without one is
|
|
77
|
+
skipped, naming why.
|
|
78
|
+
- **Its private key** reaches the proof as `CSIS_PROOF_SSH_KEY` (the key
|
|
79
|
+
itself, or a path to it; in CI a repository secret no gate reads).
|
|
80
|
+
- **ssh never touches the machine's address.** The runtime gives a
|
|
81
|
+
tunnel (`ssh_proxy_command`): on AWS, Session Manager's
|
|
82
|
+
`AWS-StartSSHSession` -- the path packer already bakes through, so a
|
|
83
|
+
private subnet with no public address and no inbound rule is fine, given
|
|
84
|
+
`ssm:StartSession` on the instance and the document and the
|
|
85
|
+
session-manager plugin; on GCE, an IAP tunnel. The host key is accepted
|
|
86
|
+
into a throwaway known-hosts file: the tunnel already chose the machine
|
|
87
|
+
by its instance id.
|
|
88
|
+
- **The checks**, stopping at the first that fails: the proof key is
|
|
89
|
+
there; the runtime gives a tunnel; the login succeeds as the proof user;
|
|
90
|
+
the group is among its groups. The record says `as: proof key`.
|
|
91
|
+
|
|
92
|
+
## Beside Okta
|
|
93
|
+
|
|
94
|
+
An `okta-tf` group builder names a posix group builder as its `posix:`
|
|
95
|
+
delegate (required since stage 75; `posix: none` is the explicit way to
|
|
96
|
+
have none). Its groups stay OPA's -- created there, members kept there --
|
|
97
|
+
and the delegate makes them exist on the machines:
|
|
98
|
+
|
|
99
|
+
- **OPA's agent makes accounts just-in-time**: it creates an account at a
|
|
100
|
+
login and deletes it later, and `userdel` drops every supplementary
|
|
101
|
+
group (observed on coops-model-005, stage 75 step 1). A membership
|
|
102
|
+
written after an apply would not hold, so the delegate installs a
|
|
103
|
+
**login hook**: an `optional` `pam_exec` session line in
|
|
104
|
+
`/etc/pam.d/sshd` runs `/usr/local/sbin/csis-group-login`, which adds
|
|
105
|
+
the logging-in user to every group whose member list
|
|
106
|
+
(`/etc/csis/groups/<group>.members`) names them. It never fails a login.
|
|
107
|
+
- **The hook is baked** into every instance image of an Okta group whose
|
|
108
|
+
builder names a delegate, and **installed again by every applying run**
|
|
109
|
+
(idempotently), so a machine that stands and is never re-baked gets it.
|
|
110
|
+
- **After each applying instance run**, each running machine of such a
|
|
111
|
+
group gets the group with OPA's gid (the Okta builder asks OPA,
|
|
112
|
+
read-only), its member list (members and admins, the root group's
|
|
113
|
+
admins merged as OPA has them), and the members whose accounts stand at
|
|
114
|
+
that moment joined at once. No account is created: OPA's agent makes
|
|
115
|
+
those.
|
|
116
|
+
- **`posix_ssh_keys: true`** on the Okta builder also writes each
|
|
117
|
+
member's declared `public_keys:` to `/etc/csis/keys/<user>`, which the
|
|
118
|
+
hook installs at login -- a key path beside OPA's certificates, off by
|
|
119
|
+
default.
|
|
120
|
+
|
|
121
|
+
## Prerequisites and integration
|
|
122
|
+
|
|
123
|
+
- **Installed** with the release (it is one of the packages `cs-image-system`
|
|
124
|
+
pins); its entry point is `cs_image_system.plugins.group`, which registers
|
|
125
|
+
both builders.
|
|
126
|
+
- **Machines**: any family with the shadow tools and sudo -- AlmaLinux/RHEL
|
|
127
|
+
(`shadow-utils`, `sudo`) and Debian (`passwd`, `sudo`) do by default. The
|
|
128
|
+
bake fails at the prerequisite check otherwise, naming nothing more than
|
|
129
|
+
the missing command.
|
|
130
|
+
- **Base images**: a base whose instance images belong to posix groups must
|
|
131
|
+
declare `identity_types: [posix]` (or list it beside `okta`); `validate`
|
|
132
|
+
refuses an image whose group resolves to an identity type its base does
|
|
133
|
+
not declare.
|
|
134
|
+
- **No credentials, no network, no state.** The builders read the
|
|
135
|
+
configuration only.
|
|
136
|
+
|
|
137
|
+
## Configuration reference
|
|
138
|
+
|
|
139
|
+
A group builder and a user builder, in `cfg/group-builders.yml`:
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
group_builders:
|
|
143
|
+
- name: posix-local
|
|
144
|
+
type: posix
|
|
145
|
+
shell: /bin/bash # the login shell of this builder's users (default)
|
|
146
|
+
admin_sudo: true # the group's admins get NOPASSWD sudo (default)
|
|
147
|
+
user_builders:
|
|
148
|
+
- name: posix-users
|
|
149
|
+
type: posix
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
| Builder field | Type | Default | Meaning |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `shell` | str | `/bin/bash` | the login shell given to accounts this plugin creates |
|
|
155
|
+
| `admin_sudo` | bool | `true` | a group's `admins` may run anything as root without a password (their accounts have none: keys only); `false` leaves sudo to the operator |
|
|
156
|
+
|
|
157
|
+
A group of a posix builder declares its id as `gid:`; a user of a posix user
|
|
158
|
+
builder declares `uid:` and its keys as `public_keys:`:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
groups:
|
|
162
|
+
- name: pxgroup
|
|
163
|
+
type: posix-local
|
|
164
|
+
gid: 3101
|
|
165
|
+
members: [taylor_tango, uma_uniform]
|
|
166
|
+
admins: [taylor_tango]
|
|
167
|
+
users:
|
|
168
|
+
- name: taylor_tango
|
|
169
|
+
type: posix-users
|
|
170
|
+
first_name: Taylor
|
|
171
|
+
last_name: Tango
|
|
172
|
+
uid: 3201
|
|
173
|
+
public_keys: ["ssh-ed25519 AAAA... taylor@example.invalid"]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The ids, checked at `validate` (the core's `check_posix_ids`, over the
|
|
177
|
+
claims both builders make):
|
|
178
|
+
|
|
179
|
+
- every posix group declares a `gid:` and every posix user a `uid:`, each at
|
|
180
|
+
least 1024 (below that are the system's own accounts; the `Group` model
|
|
181
|
+
refuses a lower gid when it loads, and the check holds uids to the same
|
|
182
|
+
floor);
|
|
183
|
+
- a user's private group takes the user's name and uid, so a declared group
|
|
184
|
+
of the same name with another gid is refused, naming both;
|
|
185
|
+
- two groups with one gid, or two users with one uid, are refused;
|
|
186
|
+
- names are POSIX names a machine accepts: lower case, a letter or
|
|
187
|
+
underscore first, at most 32 characters, `.`, `_` and `-` allowed.
|
|
188
|
+
|
|
189
|
+
A posix group or user that declares `attributes:` is refused: the ids are
|
|
190
|
+
`gid:` and `uid:`, not provider attributes.
|
|
191
|
+
|
|
192
|
+
## What it tests and verifies
|
|
193
|
+
|
|
194
|
+
- **In the bake** (the base image's in-bake checks): the four commands
|
|
195
|
+
exist; for an instance image, the owning group stands with its gid
|
|
196
|
+
(`getent group <name>`).
|
|
197
|
+
- **At `validate`**: the id rules above, every one named with the sides
|
|
198
|
+
that disagree; and every member or admin of a posix group must be a
|
|
199
|
+
user of a posix user builder with a `uid:` (the accounts the script
|
|
200
|
+
creates).
|
|
201
|
+
- **Its tests**: [`tests/test_posix_accounts.py`](tests/test_posix_accounts.py)
|
|
202
|
+
holds the script's parts as text and checks the whole script parses;
|
|
203
|
+
the system's `tests/test_v2_posix_identity.py` holds the builders, the
|
|
204
|
+
id resolution and the generated IaC; the container leg of `just
|
|
205
|
+
full-test` runs the script on AlmaLinux 10 and Debian, twice, against an
|
|
206
|
+
equal and a differing pre-existing group.
|
|
207
|
+
|
|
208
|
+
## When it fails
|
|
209
|
+
|
|
210
|
+
| Symptom | Meaning | Do |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `group <g>: no side supplies its id ... declare one (gid:)` at `validate` | a posix group or user without an id | declare `gid:` (group) or `uid:` (user) |
|
|
213
|
+
| `group <g>: the configuration supplies id <a> and the configuration (user-private group) supplies <b>` | a declared group shares its name with a posix user | rename one; a user's private group is the user's name |
|
|
214
|
+
| `groups <a>, <b> all have id <n>` | two names, one id | give each its own |
|
|
215
|
+
| `posix accounts: group <g> has gid <x> here; the configuration says <y>` in a bake's output (exit 3) | the base image already has that group with another gid | change the declared gid, or the base |
|
|
216
|
+
| `posix accounts: gid <n> belongs to group <h> here; the configuration gives it to <g>` (exit 3) | the base image already uses that gid for another group | choose a free gid |
|
|
217
|
+
| `command -v visudo` fails in a base bake | the family lacks sudo | install it in the OS builder's packages |
|
|
218
|
+
| `posix group <g>: <u> has no posix account` at `validate` | a member or admin of a posix group is not a posix user with a uid | declare it on a `type: posix` user builder with a `uid:`, or take it out of the group |
|
|
219
|
+
| `Instance <i>: the accounts script of group <g> FAILED (exit <n>)` in a run's log, with the script's `posix accounts:` lines above it | the machine refused (a group or user standing with another id) or could not be reached | read the lines; fix the id or the machine; the next applying run tries again |
|
|
220
|
+
| `login proof <i>: proof key: FAILED -- CSIS_PROOF_SSH_KEY is not set` | the proof user's private key is not in the environment (CI: the secret is unset) | set the secret, or take `is_service_account` off if no proof is wanted |
|
|
221
|
+
| `login proof <i>: tunnel: FAILED -- the runtime gives no ssh tunnel to <i>` | the machine is not running, or the runtime has no session mechanism | start it, or declare the runtime's `session_mechanism` |
|
|
222
|
+
| `login proof <i>: login: FAILED -- ssh <u>@<i> id: exit 255 ...` | the key was refused, or the tunnel could not open (AWS: `ssm:StartSession` on `AWS-StartSSHSession`, the session-manager plugin). In CI the tail names the role: the proof runs under the WRITE identity since stage 89, and a workflow from before 0.1.1.dev22 made it under the read-only role, which may not open a session | read the tail; check the key pairs with `public_keys:` and the role's grant; in CI, take the release whose workflow makes the proof before the read-only identity is taken back |
|
|
223
|
+
| `login proof <i>: in its group: FAILED` | the account stands but is not in the group | an applying instance run brings membership back; read its accounts log |
|
|
224
|
+
| `Instance <i>: <u> has no account here yet; not a member of <g> until it does` (a warning) | a member's account does not stand yet | nothing: the next run adds it once the account exists |
|
|
225
|
+
| `identity: group '<g>' was managed by the system but is missing from the YAML` at `validate` | a posix group was deleted from the YAML; a group the system once managed never leaves it (DESIGN N19) -- met 2026-10-04 retiring the live proof's group | put the entry back with `unmanaged: true` and no members; its gid stays claimed by the name, and nothing on any machine is touched |
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# cs-image-system-posix-identity-plugin
|
|
8
|
+
|
|
9
|
+
The posix identity plugin: groups and users that exist as **POSIX accounts on
|
|
10
|
+
the machines**, with ids the configuration declares. It registers a group
|
|
11
|
+
builder and a user builder, both `type: posix`, under the identity type
|
|
12
|
+
`posix`. Neither has a terraform root or asks a directory: the configuration
|
|
13
|
+
is the authority, and what it describes is written onto the machines.
|
|
14
|
+
|
|
15
|
+
Why it exists (stage 75): on a machine of an Okta-managed group, OPA's agent
|
|
16
|
+
creates each person's account and user-private group, but **never the group
|
|
17
|
+
that owns the machine** -- `getent group coops` on `coops-model-005` found
|
|
18
|
+
nothing, so the group's `2770` storage subtree was owned by a bare number no
|
|
19
|
+
account belonged to. This plugin makes that group exist, with the right id.
|
|
20
|
+
|
|
21
|
+
What it does, and when:
|
|
22
|
+
|
|
23
|
+
- **At the bake of an instance image** owned by a posix group, the group is
|
|
24
|
+
created with its declared `gid:` -- or adopted when it already stands with
|
|
25
|
+
that gid, or the bake stops when it stands with another (a name known to
|
|
26
|
+
two sides with two ids is a configuration error, the operator's rule).
|
|
27
|
+
- **A base image** that declares `identity_types: [posix]` checks that the
|
|
28
|
+
shadow tools and sudo are there (`groupadd`, `useradd`, `gpasswd`,
|
|
29
|
+
`visudo`); it installs nothing, since every supported family ships them.
|
|
30
|
+
- **Generated IaC** writes a posix group's gid as the number: there is no
|
|
31
|
+
identity root to read it from, so the storage and instance roots declare
|
|
32
|
+
no remote state for it.
|
|
33
|
+
- **`validate`** resolves every POSIX id the configuration claims (see
|
|
34
|
+
"Configuration reference").
|
|
35
|
+
|
|
36
|
+
- **After every applying instance run**, each launched, RUNNING machine of
|
|
37
|
+
a posix group gets the group's accounts script over the runtime's session
|
|
38
|
+
(SSM on AWS, the guest session on GCE), run as root: every member and
|
|
39
|
+
admin as an account (its `uid:`, a user-private group of the same name
|
|
40
|
+
and id, a home, exactly its `public_keys:` as `authorized_keys`), the
|
|
41
|
+
members (admins among them) as the group's members, and NOPASSWD sudo
|
|
42
|
+
for the admins unless `admin_sudo: false`. The script is
|
|
43
|
+
[`accounts.py`](src/cs_image_system/posix_identity_plugin/accounts.py)'s;
|
|
44
|
+
it is idempotent, adopts what stands equal and refuses what stands
|
|
45
|
+
different. It runs after launch rather than in the launch script on
|
|
46
|
+
purpose: the launch script is the machine's immutable snapshot, so a
|
|
47
|
+
membership change there would mean a new machine; here it means the next
|
|
48
|
+
applying run. A machine that is off waits for a run that finds it
|
|
49
|
+
running; a machine that booted this run is waited for (bounded).
|
|
50
|
+
A failure is logged as an error naming the machine and never stops the
|
|
51
|
+
run; the next applying run tries again.
|
|
52
|
+
|
|
53
|
+
## The login proof
|
|
54
|
+
|
|
55
|
+
`verify login` (CI's perform job runs it) logs into each standing machine
|
|
56
|
+
of a posix group as the group's **proof user** and checks it is in the
|
|
57
|
+
group -- access proved, not only health:
|
|
58
|
+
|
|
59
|
+
- **The proof user** is a member or admin declared on a posix user
|
|
60
|
+
builder with `is_service_account: true`, a `uid:` and its public key in
|
|
61
|
+
`public_keys:` (the first such member by name). A group without one is
|
|
62
|
+
skipped, naming why.
|
|
63
|
+
- **Its private key** reaches the proof as `CSIS_PROOF_SSH_KEY` (the key
|
|
64
|
+
itself, or a path to it; in CI a repository secret no gate reads).
|
|
65
|
+
- **ssh never touches the machine's address.** The runtime gives a
|
|
66
|
+
tunnel (`ssh_proxy_command`): on AWS, Session Manager's
|
|
67
|
+
`AWS-StartSSHSession` -- the path packer already bakes through, so a
|
|
68
|
+
private subnet with no public address and no inbound rule is fine, given
|
|
69
|
+
`ssm:StartSession` on the instance and the document and the
|
|
70
|
+
session-manager plugin; on GCE, an IAP tunnel. The host key is accepted
|
|
71
|
+
into a throwaway known-hosts file: the tunnel already chose the machine
|
|
72
|
+
by its instance id.
|
|
73
|
+
- **The checks**, stopping at the first that fails: the proof key is
|
|
74
|
+
there; the runtime gives a tunnel; the login succeeds as the proof user;
|
|
75
|
+
the group is among its groups. The record says `as: proof key`.
|
|
76
|
+
|
|
77
|
+
## Beside Okta
|
|
78
|
+
|
|
79
|
+
An `okta-tf` group builder names a posix group builder as its `posix:`
|
|
80
|
+
delegate (required since stage 75; `posix: none` is the explicit way to
|
|
81
|
+
have none). Its groups stay OPA's -- created there, members kept there --
|
|
82
|
+
and the delegate makes them exist on the machines:
|
|
83
|
+
|
|
84
|
+
- **OPA's agent makes accounts just-in-time**: it creates an account at a
|
|
85
|
+
login and deletes it later, and `userdel` drops every supplementary
|
|
86
|
+
group (observed on coops-model-005, stage 75 step 1). A membership
|
|
87
|
+
written after an apply would not hold, so the delegate installs a
|
|
88
|
+
**login hook**: an `optional` `pam_exec` session line in
|
|
89
|
+
`/etc/pam.d/sshd` runs `/usr/local/sbin/csis-group-login`, which adds
|
|
90
|
+
the logging-in user to every group whose member list
|
|
91
|
+
(`/etc/csis/groups/<group>.members`) names them. It never fails a login.
|
|
92
|
+
- **The hook is baked** into every instance image of an Okta group whose
|
|
93
|
+
builder names a delegate, and **installed again by every applying run**
|
|
94
|
+
(idempotently), so a machine that stands and is never re-baked gets it.
|
|
95
|
+
- **After each applying instance run**, each running machine of such a
|
|
96
|
+
group gets the group with OPA's gid (the Okta builder asks OPA,
|
|
97
|
+
read-only), its member list (members and admins, the root group's
|
|
98
|
+
admins merged as OPA has them), and the members whose accounts stand at
|
|
99
|
+
that moment joined at once. No account is created: OPA's agent makes
|
|
100
|
+
those.
|
|
101
|
+
- **`posix_ssh_keys: true`** on the Okta builder also writes each
|
|
102
|
+
member's declared `public_keys:` to `/etc/csis/keys/<user>`, which the
|
|
103
|
+
hook installs at login -- a key path beside OPA's certificates, off by
|
|
104
|
+
default.
|
|
105
|
+
|
|
106
|
+
## Prerequisites and integration
|
|
107
|
+
|
|
108
|
+
- **Installed** with the release (it is one of the packages `cs-image-system`
|
|
109
|
+
pins); its entry point is `cs_image_system.plugins.group`, which registers
|
|
110
|
+
both builders.
|
|
111
|
+
- **Machines**: any family with the shadow tools and sudo -- AlmaLinux/RHEL
|
|
112
|
+
(`shadow-utils`, `sudo`) and Debian (`passwd`, `sudo`) do by default. The
|
|
113
|
+
bake fails at the prerequisite check otherwise, naming nothing more than
|
|
114
|
+
the missing command.
|
|
115
|
+
- **Base images**: a base whose instance images belong to posix groups must
|
|
116
|
+
declare `identity_types: [posix]` (or list it beside `okta`); `validate`
|
|
117
|
+
refuses an image whose group resolves to an identity type its base does
|
|
118
|
+
not declare.
|
|
119
|
+
- **No credentials, no network, no state.** The builders read the
|
|
120
|
+
configuration only.
|
|
121
|
+
|
|
122
|
+
## Configuration reference
|
|
123
|
+
|
|
124
|
+
A group builder and a user builder, in `cfg/group-builders.yml`:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
group_builders:
|
|
128
|
+
- name: posix-local
|
|
129
|
+
type: posix
|
|
130
|
+
shell: /bin/bash # the login shell of this builder's users (default)
|
|
131
|
+
admin_sudo: true # the group's admins get NOPASSWD sudo (default)
|
|
132
|
+
user_builders:
|
|
133
|
+
- name: posix-users
|
|
134
|
+
type: posix
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Builder field | Type | Default | Meaning |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| `shell` | str | `/bin/bash` | the login shell given to accounts this plugin creates |
|
|
140
|
+
| `admin_sudo` | bool | `true` | a group's `admins` may run anything as root without a password (their accounts have none: keys only); `false` leaves sudo to the operator |
|
|
141
|
+
|
|
142
|
+
A group of a posix builder declares its id as `gid:`; a user of a posix user
|
|
143
|
+
builder declares `uid:` and its keys as `public_keys:`:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
groups:
|
|
147
|
+
- name: pxgroup
|
|
148
|
+
type: posix-local
|
|
149
|
+
gid: 3101
|
|
150
|
+
members: [taylor_tango, uma_uniform]
|
|
151
|
+
admins: [taylor_tango]
|
|
152
|
+
users:
|
|
153
|
+
- name: taylor_tango
|
|
154
|
+
type: posix-users
|
|
155
|
+
first_name: Taylor
|
|
156
|
+
last_name: Tango
|
|
157
|
+
uid: 3201
|
|
158
|
+
public_keys: ["ssh-ed25519 AAAA... taylor@example.invalid"]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The ids, checked at `validate` (the core's `check_posix_ids`, over the
|
|
162
|
+
claims both builders make):
|
|
163
|
+
|
|
164
|
+
- every posix group declares a `gid:` and every posix user a `uid:`, each at
|
|
165
|
+
least 1024 (below that are the system's own accounts; the `Group` model
|
|
166
|
+
refuses a lower gid when it loads, and the check holds uids to the same
|
|
167
|
+
floor);
|
|
168
|
+
- a user's private group takes the user's name and uid, so a declared group
|
|
169
|
+
of the same name with another gid is refused, naming both;
|
|
170
|
+
- two groups with one gid, or two users with one uid, are refused;
|
|
171
|
+
- names are POSIX names a machine accepts: lower case, a letter or
|
|
172
|
+
underscore first, at most 32 characters, `.`, `_` and `-` allowed.
|
|
173
|
+
|
|
174
|
+
A posix group or user that declares `attributes:` is refused: the ids are
|
|
175
|
+
`gid:` and `uid:`, not provider attributes.
|
|
176
|
+
|
|
177
|
+
## What it tests and verifies
|
|
178
|
+
|
|
179
|
+
- **In the bake** (the base image's in-bake checks): the four commands
|
|
180
|
+
exist; for an instance image, the owning group stands with its gid
|
|
181
|
+
(`getent group <name>`).
|
|
182
|
+
- **At `validate`**: the id rules above, every one named with the sides
|
|
183
|
+
that disagree; and every member or admin of a posix group must be a
|
|
184
|
+
user of a posix user builder with a `uid:` (the accounts the script
|
|
185
|
+
creates).
|
|
186
|
+
- **Its tests**: [`tests/test_posix_accounts.py`](tests/test_posix_accounts.py)
|
|
187
|
+
holds the script's parts as text and checks the whole script parses;
|
|
188
|
+
the system's `tests/test_v2_posix_identity.py` holds the builders, the
|
|
189
|
+
id resolution and the generated IaC; the container leg of `just
|
|
190
|
+
full-test` runs the script on AlmaLinux 10 and Debian, twice, against an
|
|
191
|
+
equal and a differing pre-existing group.
|
|
192
|
+
|
|
193
|
+
## When it fails
|
|
194
|
+
|
|
195
|
+
| Symptom | Meaning | Do |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `group <g>: no side supplies its id ... declare one (gid:)` at `validate` | a posix group or user without an id | declare `gid:` (group) or `uid:` (user) |
|
|
198
|
+
| `group <g>: the configuration supplies id <a> and the configuration (user-private group) supplies <b>` | a declared group shares its name with a posix user | rename one; a user's private group is the user's name |
|
|
199
|
+
| `groups <a>, <b> all have id <n>` | two names, one id | give each its own |
|
|
200
|
+
| `posix accounts: group <g> has gid <x> here; the configuration says <y>` in a bake's output (exit 3) | the base image already has that group with another gid | change the declared gid, or the base |
|
|
201
|
+
| `posix accounts: gid <n> belongs to group <h> here; the configuration gives it to <g>` (exit 3) | the base image already uses that gid for another group | choose a free gid |
|
|
202
|
+
| `command -v visudo` fails in a base bake | the family lacks sudo | install it in the OS builder's packages |
|
|
203
|
+
| `posix group <g>: <u> has no posix account` at `validate` | a member or admin of a posix group is not a posix user with a uid | declare it on a `type: posix` user builder with a `uid:`, or take it out of the group |
|
|
204
|
+
| `Instance <i>: the accounts script of group <g> FAILED (exit <n>)` in a run's log, with the script's `posix accounts:` lines above it | the machine refused (a group or user standing with another id) or could not be reached | read the lines; fix the id or the machine; the next applying run tries again |
|
|
205
|
+
| `login proof <i>: proof key: FAILED -- CSIS_PROOF_SSH_KEY is not set` | the proof user's private key is not in the environment (CI: the secret is unset) | set the secret, or take `is_service_account` off if no proof is wanted |
|
|
206
|
+
| `login proof <i>: tunnel: FAILED -- the runtime gives no ssh tunnel to <i>` | the machine is not running, or the runtime has no session mechanism | start it, or declare the runtime's `session_mechanism` |
|
|
207
|
+
| `login proof <i>: login: FAILED -- ssh <u>@<i> id: exit 255 ...` | the key was refused, or the tunnel could not open (AWS: `ssm:StartSession` on `AWS-StartSSHSession`, the session-manager plugin). In CI the tail names the role: the proof runs under the WRITE identity since stage 89, and a workflow from before 0.1.1.dev22 made it under the read-only role, which may not open a session | read the tail; check the key pairs with `public_keys:` and the role's grant; in CI, take the release whose workflow makes the proof before the read-only identity is taken back |
|
|
208
|
+
| `login proof <i>: in its group: FAILED` | the account stands but is not in the group | an applying instance run brings membership back; read its accounts log |
|
|
209
|
+
| `Instance <i>: <u> has no account here yet; not a member of <g> until it does` (a warning) | a member's account does not stand yet | nothing: the next run adds it once the account exists |
|
|
210
|
+
| `identity: group '<g>' was managed by the system but is missing from the YAML` at `validate` | a posix group was deleted from the YAML; a group the system once managed never leaves it (DESIGN N19) -- met 2026-10-04 retiring the live proof's group | put the entry back with `unmanaged: true` and no members; its gid stays claimed by the name, and nothing on any machine is touched |
|