Add readonly SSH audit user role and playbooks
This commit is contained in:
parent
b8d93ec31c
commit
19e1f4ef1d
13
README.md
13
README.md
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
The traffic billing stack now has a single aggregate playbook:
|
The traffic billing stack now has a single aggregate playbook:
|
||||||
|
|
||||||
`deploy_traffic_billing_stack.yml`
|
`deploy_svc_plus_core_services_stack.yml`
|
||||||
|
|
||||||
It orchestrates these existing playbooks in dependency order:
|
It orchestrates these existing playbooks in dependency order:
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ It orchestrates these existing playbooks in dependency order:
|
|||||||
3. `deploy_accounts_svc_plus.yml`
|
3. `deploy_accounts_svc_plus.yml`
|
||||||
4. `deploy_console_svc_plus.yml`
|
4. `deploy_console_svc_plus.yml`
|
||||||
5. `deploy_agent_svc_plus.yml`
|
5. `deploy_agent_svc_plus.yml`
|
||||||
|
6. `deploy_xworkmate_bridge_vhosts.yml`
|
||||||
|
|
||||||
### Full stack deploy
|
### Full stack deploy
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ export DATABASE_URL=postgres://...
|
|||||||
export FRONTEND_IMAGE=ghcr.io/x-evor/dashboard:latest
|
export FRONTEND_IMAGE=ghcr.io/x-evor/dashboard:latest
|
||||||
export STACK_TARGET_HOST=jp_xhttp_contabo_host
|
export STACK_TARGET_HOST=jp_xhttp_contabo_host
|
||||||
export console_service_sync_dns=true
|
export console_service_sync_dns=true
|
||||||
ansible-playbook -i inventory.ini deploy_traffic_billing_stack.yml
|
ansible-playbook -i inventory.ini deploy_svc_plus_core_services_stack.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
`STACK_ENV_FILE=./.env` is optional. Use it when you want the aggregate playbook to read a local `.env` file; GitHub Actions or other CI runners can skip it and pass values with `-e` instead.
|
`STACK_ENV_FILE=./.env` is optional. Use it when you want the aggregate playbook to read a local `.env` file; GitHub Actions or other CI runners can skip it and pass values with `-e` instead.
|
||||||
@ -39,7 +40,7 @@ export INTERNAL_SERVICE_TOKEN=...
|
|||||||
export DATABASE_URL=postgres://...
|
export DATABASE_URL=postgres://...
|
||||||
export FRONTEND_IMAGE=ghcr.io/x-evor/dashboard:latest
|
export FRONTEND_IMAGE=ghcr.io/x-evor/dashboard:latest
|
||||||
export console_service_sync_dns=true
|
export console_service_sync_dns=true
|
||||||
ansible-playbook -i inventory.ini -l jp_xhttp_contabo_host deploy_traffic_billing_stack.yml
|
ansible-playbook -i inventory.ini -l jp_xhttp_contabo_host deploy_svc_plus_core_services_stack.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy only selected services
|
### Deploy only selected services
|
||||||
@ -51,14 +52,15 @@ Use `STACK_SERVICES` with a comma-separated list:
|
|||||||
- `accounts`
|
- `accounts`
|
||||||
- `console`
|
- `console`
|
||||||
- `agent`
|
- `agent`
|
||||||
|
- `xworkmate-bridge`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/playbooks
|
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/playbooks
|
||||||
export STACK_TARGET_HOST=jp-xhttp-contabo.svc.plus
|
export STACK_TARGET_HOST=jp-xhttp-contabo.svc.plus
|
||||||
export STACK_SERVICES=xray-exporter,billing-service,agent
|
export STACK_SERVICES=xray-exporter,billing-service,agent,xworkmate-bridge
|
||||||
export INTERNAL_SERVICE_TOKEN=...
|
export INTERNAL_SERVICE_TOKEN=...
|
||||||
export DATABASE_URL=postgres://...
|
export DATABASE_URL=postgres://...
|
||||||
ansible-playbook -i inventory.ini -l jp_xhttp_contabo_host deploy_traffic_billing_stack.yml
|
ansible-playbook -i inventory.ini -l jp_xhttp_contabo_host deploy_svc_plus_core_services_stack.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
@ -68,6 +70,7 @@ ansible-playbook -i inventory.ini -l jp_xhttp_contabo_host deploy_traffic_billin
|
|||||||
- `console` now writes a Caddy fragment named like `<server-name>-<release_id>-<hostname>-<domain>.caddy` instead of managing the Caddy service container itself.
|
- `console` now writes a Caddy fragment named like `<server-name>-<release_id>-<hostname>-<domain>.caddy` instead of managing the Caddy service container itself.
|
||||||
- `billing-service` requires `DATABASE_URL`.
|
- `billing-service` requires `DATABASE_URL`.
|
||||||
- `xray-exporter` and `agent` require `INTERNAL_SERVICE_TOKEN`.
|
- `xray-exporter` and `agent` require `INTERNAL_SERVICE_TOKEN`.
|
||||||
|
- `xworkmate-bridge` accepts `XWORKMATE_BRIDGE_HOSTS`, and also follows `STACK_TARGET_HOST` when you want to deploy the whole stack to one host.
|
||||||
|
|
||||||
### Deploy console to a specific host and sync DNS
|
### Deploy console to a specific host and sync DNS
|
||||||
|
|
||||||
|
|||||||
24
create_audit_user.yml
Normal file
24
create_audit_user.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
- name: Create a root-managed SSH audit user on selected hosts
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
vars:
|
||||||
|
ansible_user: "{{ lookup('env', 'BOOTSTRAP_ROOT_USER') | default('root', true) }}"
|
||||||
|
ansible_password: "{{ lookup('env', 'BOOTSTRAP_ROOT_PASSWORD') | default(omit, true) }}"
|
||||||
|
ansible_become_password: "{{ lookup('env', 'BOOTSTRAP_BECOME_PASSWORD') | default(omit, true) }}"
|
||||||
|
|
||||||
|
readonly_ssh_user_name: "{{ lookup('env', 'READONLY_SSH_USER_NAME') | default('readonly', true) }}"
|
||||||
|
readonly_ssh_user_profile: audit
|
||||||
|
readonly_ssh_user_lock_password: true
|
||||||
|
readonly_ssh_user_manage_sudoers: true
|
||||||
|
readonly_ssh_user_authorized_keys: >-
|
||||||
|
{{
|
||||||
|
[lookup('env', 'READONLY_SSH_USER_PUBLIC_KEY')]
|
||||||
|
if lookup('env', 'READONLY_SSH_USER_PUBLIC_KEY') | default('', true) | length > 0
|
||||||
|
else []
|
||||||
|
}}
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: readonly_ssh_user
|
||||||
25
create_readonly_ssh_user.yml
Normal file
25
create_readonly_ssh_user.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
- name: Create a readonly SSH user on selected hosts
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
vars:
|
||||||
|
ansible_user: "{{ lookup('env', 'BOOTSTRAP_ROOT_USER') | default('root', true) }}"
|
||||||
|
ansible_password: "{{ lookup('env', 'BOOTSTRAP_ROOT_PASSWORD') | default(omit, true) }}"
|
||||||
|
ansible_become_password: "{{ lookup('env', 'BOOTSTRAP_BECOME_PASSWORD') | default(omit, true) }}"
|
||||||
|
|
||||||
|
readonly_ssh_user_name: "{{ lookup('env', 'READONLY_SSH_USER_NAME') | default('readonly', true) }}"
|
||||||
|
readonly_ssh_user_profile: "{{ lookup('env', 'READONLY_SSH_USER_PROFILE') | default('readonly', true) }}"
|
||||||
|
readonly_ssh_user_password_hash: "{{ lookup('env', 'READONLY_SSH_USER_PASSWORD_HASH') | default('', true) }}"
|
||||||
|
readonly_ssh_user_lock_password: "{{ lookup('env', 'READONLY_SSH_LOCK_PASSWORD') | default('true', true) | bool }}"
|
||||||
|
readonly_ssh_user_manage_sudoers: "{{ lookup('env', 'READONLY_SSH_ENABLE_SUDO') | default('false', true) | bool }}"
|
||||||
|
readonly_ssh_user_authorized_keys: >-
|
||||||
|
{{
|
||||||
|
[lookup('env', 'READONLY_SSH_USER_PUBLIC_KEY')]
|
||||||
|
if lookup('env', 'READONLY_SSH_USER_PUBLIC_KEY') | default('', true) | length > 0
|
||||||
|
else []
|
||||||
|
}}
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: readonly_ssh_user
|
||||||
@ -13,7 +13,7 @@
|
|||||||
| default('', true), true) }}
|
| default('', true), true) }}
|
||||||
stack_services: >-
|
stack_services: >-
|
||||||
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
||||||
| default('xray-exporter,billing-service,accounts,console,agent', true) }}
|
| default('xray-exporter,billing-service,accounts,console,agent,xworkmate-bridge', true) }}
|
||||||
when: "'xray-exporter' in (stack_services.split(',') | map('trim') | list)"
|
when: "'xray-exporter' in (stack_services.split(',') | map('trim') | list)"
|
||||||
|
|
||||||
- import_playbook: deploy_billing_service.yml
|
- import_playbook: deploy_billing_service.yml
|
||||||
@ -35,7 +35,7 @@
|
|||||||
| default('http://127.0.0.1:8080', true), true) }}
|
| default('http://127.0.0.1:8080', true), true) }}
|
||||||
stack_services: >-
|
stack_services: >-
|
||||||
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
||||||
| default('xray-exporter,billing-service,accounts,console,agent', true) }}
|
| default('xray-exporter,billing-service,accounts,console,agent,xworkmate-bridge', true) }}
|
||||||
when: "'billing-service' in (stack_services.split(',') | map('trim') | list)"
|
when: "'billing-service' in (stack_services.split(',') | map('trim') | list)"
|
||||||
|
|
||||||
- import_playbook: deploy_accounts_svc_plus.yml
|
- import_playbook: deploy_accounts_svc_plus.yml
|
||||||
@ -57,7 +57,7 @@
|
|||||||
| default('latest', true), true) }}
|
| default('latest', true), true) }}
|
||||||
stack_services: >-
|
stack_services: >-
|
||||||
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
||||||
| default('xray-exporter,billing-service,accounts,console,agent', true) }}
|
| default('xray-exporter,billing-service,accounts,console,agent,xworkmate-bridge', true) }}
|
||||||
when: "'accounts' in (stack_services.split(',') | map('trim') | list)"
|
when: "'accounts' in (stack_services.split(',') | map('trim') | list)"
|
||||||
|
|
||||||
- import_playbook: deploy_console_svc_plus.yml
|
- import_playbook: deploy_console_svc_plus.yml
|
||||||
@ -87,7 +87,7 @@
|
|||||||
| default('', true), true) }}
|
| default('', true), true) }}
|
||||||
stack_services: >-
|
stack_services: >-
|
||||||
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
||||||
| default('xray-exporter,billing-service,accounts,console,agent', true) }}
|
| default('xray-exporter,billing-service,accounts,console,agent,xworkmate-bridge', true) }}
|
||||||
when: "'console' in (stack_services.split(',') | map('trim') | list)"
|
when: "'console' in (stack_services.split(',') | map('trim') | list)"
|
||||||
|
|
||||||
- import_playbook: deploy_agent_svc_plus.yml
|
- import_playbook: deploy_agent_svc_plus.yml
|
||||||
@ -109,5 +109,16 @@
|
|||||||
| default('http://127.0.0.1:8081', true), true) }}
|
| default('http://127.0.0.1:8081', true), true) }}
|
||||||
stack_services: >-
|
stack_services: >-
|
||||||
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
||||||
| default('xray-exporter,billing-service,accounts,console,agent', true) }}
|
| default('xray-exporter,billing-service,accounts,console,agent,xworkmate-bridge', true) }}
|
||||||
when: "'agent' in (stack_services.split(',') | map('trim') | list)"
|
when: "'agent' in (stack_services.split(',') | map('trim') | list)"
|
||||||
|
|
||||||
|
- import_playbook: deploy_xworkmate_bridge_vhosts.yml
|
||||||
|
vars:
|
||||||
|
xworkmate_bridge_hosts: >-
|
||||||
|
{{ lookup('ansible.builtin.env', 'STACK_TARGET_HOST')
|
||||||
|
| default(lookup('ansible.builtin.env', 'XWORKMATE_BRIDGE_HOSTS')
|
||||||
|
| default('all', true), true) }}
|
||||||
|
stack_services: >-
|
||||||
|
{{ lookup('ansible.builtin.env', 'STACK_SERVICES')
|
||||||
|
| default('xray-exporter,billing-service,accounts,console,agent,xworkmate-bridge', true) }}
|
||||||
|
when: "'xworkmate-bridge' in (stack_services.split(',') | map('trim') | list)"
|
||||||
128
roles/readonly_ssh_user/README.md
Normal file
128
roles/readonly_ssh_user/README.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# readonly_ssh_user
|
||||||
|
|
||||||
|
Create a remote SSH login user that can inspect the host as an unprivileged account but cannot modify protected system configuration.
|
||||||
|
|
||||||
|
## TLDR
|
||||||
|
|
||||||
|
export READONLY_SSH_USER_NAME=readonly Or auditor
|
||||||
|
export READONLY_SSH_LOCK_PASSWORD=true
|
||||||
|
export READONLY_SSH_ENABLE_SUDO=true
|
||||||
|
export READONLY_SSH_USER_PUBLIC_KEY='你的ssh公钥'
|
||||||
|
|
||||||
|
ansible-playbook -i inventory.ini create_readonly_ssh_user.yml --limit jp-xhttp-contabo.svc.plus
|
||||||
|
|
||||||
|
默认是 `readonly` profile。要创建更实用的“审计用户”,加上:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export READONLY_SSH_USER_PROFILE=audit
|
||||||
|
```
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- Creates a normal Linux user with no `sudo` or other privileged groups by default
|
||||||
|
- Locks the account password by default so password login is not usable
|
||||||
|
- Supports login with either a password hash or one or more SSH public keys
|
||||||
|
- Writes an `sshd_config.d` drop-in with per-user SSH restrictions
|
||||||
|
- Removes the user from common privileged groups such as `sudo`, `wheel`, and `docker`
|
||||||
|
- Optionally grants `sudo` for a tightly scoped read-only command whitelist
|
||||||
|
- Supports two profiles:
|
||||||
|
- `readonly`: minimal read-only sudo whitelist
|
||||||
|
- `audit`: broader inspection whitelist for logs, units, network, users, and common service status checks
|
||||||
|
|
||||||
|
## Important limitation
|
||||||
|
|
||||||
|
Linux 没法只靠“普通用户 + sudo 白名单”创建一个绝对不可变更的一般用途 SSH 审计用户。这个 role 提供的是“受限审计用户”,不是内核级只读沙箱。
|
||||||
|
|
||||||
|
This role creates an unprivileged user, not a kernel-enforced immutable sandbox. The user can still write to:
|
||||||
|
|
||||||
|
- its own home directory
|
||||||
|
- any path that is already world-writable or group-writable to one of its groups
|
||||||
|
|
||||||
|
It will not be able to change protected system configuration unless you explicitly grant extra privileges.
|
||||||
|
|
||||||
|
If you enable limited sudo, the user still does not join the `sudo` group. Instead, the role writes a dedicated sudoers file with only approved read-only commands.
|
||||||
|
|
||||||
|
For the default hardening mode used for `readonly`, the intended model is:
|
||||||
|
|
||||||
|
- `passwd -l` style locked password
|
||||||
|
- SSH public key login only
|
||||||
|
- no password-based SSH login
|
||||||
|
- account lifecycle managed by `root`
|
||||||
|
|
||||||
|
`audit` profile 适合:
|
||||||
|
|
||||||
|
- 看系统配置
|
||||||
|
- 看服务状态
|
||||||
|
- 看日志
|
||||||
|
- 看网络和用户状态
|
||||||
|
- 做人工巡检
|
||||||
|
|
||||||
|
`audit` profile 不适合:
|
||||||
|
|
||||||
|
- 真正执行 `ansible-playbook` 去修配置
|
||||||
|
- 依赖 `become` 修改远端文件
|
||||||
|
- 重启服务、安装包、改权限、写 `/etc`
|
||||||
|
|
||||||
|
如果目标是“能用 Ansible 修复”,那已经不是审计用户,而是受限运维用户,需要单独设计 sudoers 白名单。
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
- `readonly_ssh_user_profile`: `readonly` or `audit`, default `readonly`
|
||||||
|
- `readonly_ssh_user_name`: username, default `readonly`
|
||||||
|
- `readonly_ssh_user_password_hash`: password hash for SSH password login
|
||||||
|
- `readonly_ssh_user_lock_password`: lock the local password, default `true`
|
||||||
|
- `readonly_ssh_user_authorized_keys`: list of SSH public keys for key-based login
|
||||||
|
- `readonly_ssh_user_groups`: supplementary groups to keep, default `[]`
|
||||||
|
- `readonly_ssh_user_manage_sshd`: whether to write an SSH Match block, default `true`
|
||||||
|
- `readonly_ssh_user_manage_sudoers`: whether to create a limited sudoers rule, default `false`
|
||||||
|
- `readonly_ssh_user_sudo_commands_readonly`: minimal whitelist
|
||||||
|
- `readonly_ssh_user_sudo_commands_audit`: broader audit whitelist
|
||||||
|
- `readonly_ssh_user_sudo_commands`: final whitelist, auto-derived from profile unless you override it
|
||||||
|
- `readonly_ssh_user_allow_tcp_forwarding`: default `false`
|
||||||
|
- `readonly_ssh_user_x11_forwarding`: default `false`
|
||||||
|
- `readonly_ssh_user_allow_agent_forwarding`: default `false`
|
||||||
|
- `readonly_ssh_user_force_command`: optional forced command
|
||||||
|
|
||||||
|
## Example playbook
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- hosts: jp_xhttp_contabo_host
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- role: readonly_ssh_user
|
||||||
|
vars:
|
||||||
|
readonly_ssh_user_profile: audit
|
||||||
|
readonly_ssh_user_name: readonly
|
||||||
|
readonly_ssh_user_manage_sudoers: true
|
||||||
|
readonly_ssh_user_authorized_keys:
|
||||||
|
- "ssh-ed25519 AAAA..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## About ansible-playbook -D -C
|
||||||
|
|
||||||
|
这个角色创建的 `audit` 用户可以辅助“看”:
|
||||||
|
|
||||||
|
- 读取配置
|
||||||
|
- 看日志和 unit 状态
|
||||||
|
- 运行手工审计命令
|
||||||
|
|
||||||
|
但它不应该被视为可以可靠执行 `ansible-playbook -D -C` 来“修正 role / playbook”的用户。原因是很多 Ansible 任务即使在 check mode 下,仍然需要更广的 `become` 能力、远端临时文件操作和模块执行权限。
|
||||||
|
|
||||||
|
如果你需要“Ansible 可修复但受限”的账号,建议单独建一个 `operator_lite` 角色,而不是继续扩大审计账号权限。
|
||||||
|
|
||||||
|
## Password hash example
|
||||||
|
|
||||||
|
Generate a password hash locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c 'import crypt, getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Root-managed SSH-only mode
|
||||||
|
|
||||||
|
The default role behavior now matches this model:
|
||||||
|
|
||||||
|
- the `readonly` user has a locked password
|
||||||
|
- SSH login is expected to happen by public key only
|
||||||
|
- `PasswordAuthentication no` is enforced for that user in the SSH Match block
|
||||||
|
- password resets and account management should be performed by `root`
|
||||||
111
roles/readonly_ssh_user/defaults/main.yml
Normal file
111
roles/readonly_ssh_user/defaults/main.yml
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
---
|
||||||
|
readonly_ssh_user_profile: readonly
|
||||||
|
readonly_ssh_user_name: readonly
|
||||||
|
readonly_ssh_user_comment: "Read-only SSH user"
|
||||||
|
readonly_ssh_user_shell: /bin/bash
|
||||||
|
readonly_ssh_user_home: "/home/{{ readonly_ssh_user_name }}"
|
||||||
|
readonly_ssh_user_create_home: true
|
||||||
|
readonly_ssh_user_password_hash: ""
|
||||||
|
readonly_ssh_user_lock_password: true
|
||||||
|
readonly_ssh_user_authorized_keys: []
|
||||||
|
readonly_ssh_user_append_groups: false
|
||||||
|
readonly_ssh_user_groups: []
|
||||||
|
readonly_ssh_user_restricted_groups:
|
||||||
|
- sudo
|
||||||
|
- wheel
|
||||||
|
- adm
|
||||||
|
- docker
|
||||||
|
- lxd
|
||||||
|
- libvirt
|
||||||
|
- root
|
||||||
|
readonly_ssh_user_state: present
|
||||||
|
readonly_ssh_user_manage_sshd: true
|
||||||
|
readonly_ssh_user_sshd_dropin_dir: /etc/ssh/sshd_config.d
|
||||||
|
readonly_ssh_user_sshd_dropin_file: "99-{{ readonly_ssh_user_name }}-readonly.conf"
|
||||||
|
readonly_ssh_user_allow_tcp_forwarding: false
|
||||||
|
readonly_ssh_user_x11_forwarding: false
|
||||||
|
readonly_ssh_user_permit_tunnel: false
|
||||||
|
readonly_ssh_user_permit_tty: true
|
||||||
|
readonly_ssh_user_allow_agent_forwarding: false
|
||||||
|
readonly_ssh_user_password_authentication: false
|
||||||
|
readonly_ssh_user_pubkey_authentication: true
|
||||||
|
readonly_ssh_user_force_command: ""
|
||||||
|
readonly_ssh_service_name_override: ""
|
||||||
|
readonly_ssh_user_manage_sudoers: false
|
||||||
|
readonly_ssh_user_sudo_nopasswd: true
|
||||||
|
readonly_ssh_user_sudoers_file: "/etc/sudoers.d/{{ readonly_ssh_user_name }}-readonly"
|
||||||
|
readonly_ssh_user_sudo_commands_readonly:
|
||||||
|
- /usr/bin/cat *
|
||||||
|
- /usr/bin/head *
|
||||||
|
- /usr/bin/tail *
|
||||||
|
- /usr/bin/grep *
|
||||||
|
- /usr/bin/find *
|
||||||
|
- /usr/bin/ls *
|
||||||
|
- /usr/bin/stat *
|
||||||
|
- /usr/bin/du *
|
||||||
|
- /usr/bin/df *
|
||||||
|
- /usr/bin/ps *
|
||||||
|
- /usr/bin/ss *
|
||||||
|
- /usr/bin/free
|
||||||
|
- /usr/bin/uptime
|
||||||
|
- /usr/bin/id
|
||||||
|
- /usr/bin/uname -a
|
||||||
|
- /usr/bin/hostnamectl status
|
||||||
|
- /usr/bin/systemctl status *
|
||||||
|
- /usr/bin/systemctl show *
|
||||||
|
- /usr/bin/journalctl *
|
||||||
|
readonly_ssh_user_sudo_commands_audit:
|
||||||
|
- /usr/bin/cat *
|
||||||
|
- /usr/bin/head *
|
||||||
|
- /usr/bin/tail *
|
||||||
|
- /usr/bin/grep *
|
||||||
|
- /usr/bin/egrep *
|
||||||
|
- /usr/bin/fgrep *
|
||||||
|
- /usr/bin/find *
|
||||||
|
- /usr/bin/ls *
|
||||||
|
- /usr/bin/stat *
|
||||||
|
- /usr/bin/namei *
|
||||||
|
- /usr/bin/file *
|
||||||
|
- /usr/bin/du *
|
||||||
|
- /usr/bin/df *
|
||||||
|
- /usr/bin/ps *
|
||||||
|
- /usr/bin/ss *
|
||||||
|
- /usr/bin/free
|
||||||
|
- /usr/bin/uptime
|
||||||
|
- /usr/bin/id
|
||||||
|
- /usr/bin/uname -a
|
||||||
|
- /usr/bin/hostnamectl status
|
||||||
|
- /usr/bin/systemctl status *
|
||||||
|
- /usr/bin/systemctl show *
|
||||||
|
- /usr/bin/systemctl list-units *
|
||||||
|
- /usr/bin/systemctl list-unit-files *
|
||||||
|
- /usr/bin/systemctl cat *
|
||||||
|
- /usr/bin/journalctl *
|
||||||
|
- /usr/bin/loginctl *
|
||||||
|
- /usr/bin/env
|
||||||
|
- /usr/bin/printenv
|
||||||
|
- /usr/bin/whoami
|
||||||
|
- /usr/bin/w
|
||||||
|
- /usr/bin/who
|
||||||
|
- /usr/bin/last *
|
||||||
|
- /usr/bin/lastlog *
|
||||||
|
- /usr/bin/passwd -S *
|
||||||
|
- /usr/bin/getent *
|
||||||
|
- /usr/bin/crontab -l *
|
||||||
|
- /usr/sbin/ufw status
|
||||||
|
- /usr/sbin/ip addr *
|
||||||
|
- /usr/sbin/ip route *
|
||||||
|
- /usr/sbin/ip rule *
|
||||||
|
- /usr/sbin/iptables -S *
|
||||||
|
- /usr/sbin/ip6tables -S *
|
||||||
|
- /usr/sbin/nginx -T
|
||||||
|
- /usr/sbin/apachectl -S
|
||||||
|
- /usr/bin/docker ps *
|
||||||
|
- /usr/bin/docker images *
|
||||||
|
- /usr/bin/docker inspect *
|
||||||
|
readonly_ssh_user_sudo_commands: >-
|
||||||
|
{{
|
||||||
|
readonly_ssh_user_sudo_commands_audit
|
||||||
|
if readonly_ssh_user_profile == 'audit'
|
||||||
|
else readonly_ssh_user_sudo_commands_readonly
|
||||||
|
}}
|
||||||
33
roles/readonly_ssh_user/handlers/main.yml
Normal file
33
roles/readonly_ssh_user/handlers/main.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
- name: Validate sshd configuration syntax
|
||||||
|
ansible.builtin.command: sshd -t
|
||||||
|
changed_when: false
|
||||||
|
when: not ansible_check_mode
|
||||||
|
listen: reload sshd
|
||||||
|
|
||||||
|
- name: Collect service facts for ssh reload
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
changed_when: false
|
||||||
|
listen: reload sshd
|
||||||
|
|
||||||
|
- name: Select SSH service name for readonly user role
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
readonly_ssh_service_name: >-
|
||||||
|
{{
|
||||||
|
readonly_ssh_service_name_override
|
||||||
|
if readonly_ssh_service_name_override | length > 0
|
||||||
|
else ('ssh' if 'ssh.service' in ansible_facts.services else 'sshd')
|
||||||
|
}}
|
||||||
|
listen: reload sshd
|
||||||
|
|
||||||
|
- name: Reload SSH service
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: "{{ readonly_ssh_service_name }}"
|
||||||
|
state: reloaded
|
||||||
|
listen: reload sshd
|
||||||
|
|
||||||
|
- name: Validate sudoers syntax
|
||||||
|
ansible.builtin.command: "visudo -cf {{ readonly_ssh_user_sudoers_file }}"
|
||||||
|
changed_when: false
|
||||||
|
when: not ansible_check_mode
|
||||||
|
listen: validate sudoers
|
||||||
167
roles/readonly_ssh_user/readonly-audit-checklist.md
Normal file
167
roles/readonly_ssh_user/readonly-audit-checklist.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# readonly audit checklist
|
||||||
|
|
||||||
|
Use this checklist after creating a `readonly` or `audit` SSH user with the `readonly_ssh_user` role.
|
||||||
|
|
||||||
|
The goal is to confirm the account can inspect system information and logs, but cannot make real changes such as editing protected files, restarting services, installing packages, or changing permissions.
|
||||||
|
|
||||||
|
## 1. Login
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh readonly@jp-xhttp-contabo.svc.plus
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Verify identity and sudo scope
|
||||||
|
|
||||||
|
These commands should succeed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
whoami
|
||||||
|
id
|
||||||
|
sudo -l
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- current user is `readonly`
|
||||||
|
- user is not in `sudo`, `wheel`, `docker`, or other privileged groups unless explicitly intended
|
||||||
|
- `sudo -l` shows only the approved read-only whitelist
|
||||||
|
|
||||||
|
## 3. Verify system and environment inspection
|
||||||
|
|
||||||
|
These commands should succeed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uname -a
|
||||||
|
hostnamectl status
|
||||||
|
uptime
|
||||||
|
free -h
|
||||||
|
df -h
|
||||||
|
sudo env
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- system identity and runtime information are visible
|
||||||
|
- no write action is performed
|
||||||
|
|
||||||
|
## 4. Verify user, session, and scheduled task inspection
|
||||||
|
|
||||||
|
These commands should succeed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo getent passwd | head
|
||||||
|
sudo last -n 20
|
||||||
|
sudo lastlog | head
|
||||||
|
sudo crontab -l -u root
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- account and login history can be reviewed
|
||||||
|
- root cron can be inspected if included in the sudo whitelist
|
||||||
|
|
||||||
|
## 5. Verify network inspection
|
||||||
|
|
||||||
|
These commands should succeed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ip addr
|
||||||
|
sudo ip route
|
||||||
|
sudo ss -tulpn
|
||||||
|
sudo iptables -S
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- interface, route, listening port, and firewall rule information is visible
|
||||||
|
|
||||||
|
## 6. Verify service and log inspection
|
||||||
|
|
||||||
|
These commands should succeed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status ssh
|
||||||
|
sudo systemctl list-units --type=service --no-pager | head -50
|
||||||
|
sudo journalctl -u ssh -n 50 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- service status is visible
|
||||||
|
- logs can be inspected
|
||||||
|
- no service control actions are available
|
||||||
|
|
||||||
|
## 7. Verify configuration file inspection
|
||||||
|
|
||||||
|
These commands should succeed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cat /etc/ssh/sshd_config
|
||||||
|
sudo cat /etc/passwd
|
||||||
|
sudo nginx -T
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- protected configuration can be inspected through approved read-only commands
|
||||||
|
- config dump commands such as `nginx -T` work if the binary exists and is whitelisted
|
||||||
|
|
||||||
|
## 8. Verify container inspection if Docker is present
|
||||||
|
|
||||||
|
These commands should succeed when Docker is installed and included in the whitelist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker ps
|
||||||
|
sudo docker images
|
||||||
|
sudo docker inspect <container_or_image>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- container and image metadata is visible
|
||||||
|
- no container lifecycle actions are permitted
|
||||||
|
|
||||||
|
## 9. Verify modification attempts are blocked
|
||||||
|
|
||||||
|
These commands should fail:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart ssh
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
sudo touch /etc/readonly-test
|
||||||
|
sudo cp /etc/hosts /etc/hosts.bak2
|
||||||
|
sudo chmod 644 /etc/ssh/sshd_config
|
||||||
|
sudo useradd test123
|
||||||
|
sudo apt install -y tree
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- restart and reload commands fail
|
||||||
|
- writes under `/etc` fail
|
||||||
|
- permission changes fail
|
||||||
|
- package installation fails
|
||||||
|
- user creation fails
|
||||||
|
|
||||||
|
## 10. Interpretation
|
||||||
|
|
||||||
|
The account is behaving correctly if:
|
||||||
|
|
||||||
|
- inspection commands succeed
|
||||||
|
- modification commands fail
|
||||||
|
- the account can read most operational information needed for audits
|
||||||
|
- the account still cannot apply real remediation
|
||||||
|
|
||||||
|
If `ansible-playbook -D -C` is the target use case, this account is still not the right choice for remediation. It is an audit account, not a limited operator account.
|
||||||
|
|
||||||
|
## 11. Optional follow-up
|
||||||
|
|
||||||
|
If the account is too weak for your audit process:
|
||||||
|
|
||||||
|
- add only specific additional read-only commands to the sudo whitelist
|
||||||
|
- avoid broad additions such as editor binaries, package managers, service restart commands, shell escapes, or file write utilities
|
||||||
|
|
||||||
|
If the account is too strong:
|
||||||
|
|
||||||
|
- remove commands from the `audit` whitelist
|
||||||
|
- create a stricter profile derived from `readonly`
|
||||||
114
roles/readonly_ssh_user/tasks/main.yml
Normal file
114
roles/readonly_ssh_user/tasks/main.yml
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
- name: Assert readonly SSH user profile is supported
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- readonly_ssh_user_profile in ['readonly', 'audit']
|
||||||
|
fail_msg: "readonly_ssh_user_profile must be one of: readonly, audit"
|
||||||
|
|
||||||
|
- name: Assert readonly SSH user has at least one login method
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- readonly_ssh_user_authorized_keys | length > 0 or (readonly_ssh_user_password_hash | length > 0 and not readonly_ssh_user_lock_password)
|
||||||
|
fail_msg: >-
|
||||||
|
Set readonly_ssh_user_authorized_keys for SSH key login, or provide an
|
||||||
|
unlocked readonly_ssh_user_password_hash if you intentionally want password login.
|
||||||
|
|
||||||
|
- name: Set role comment based on selected profile when default is unchanged
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
readonly_ssh_user_effective_comment: >-
|
||||||
|
{{
|
||||||
|
'Audit SSH user'
|
||||||
|
if readonly_ssh_user_profile == 'audit'
|
||||||
|
else 'Read-only SSH user'
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Compute effective sudo command whitelist
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
readonly_ssh_user_effective_sudo_commands: "{{ readonly_ssh_user_sudo_commands }}"
|
||||||
|
|
||||||
|
- name: Ensure readonly SSH user exists
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ readonly_ssh_user_name }}"
|
||||||
|
comment: >-
|
||||||
|
{{
|
||||||
|
readonly_ssh_user_comment
|
||||||
|
if readonly_ssh_user_comment != 'Read-only SSH user'
|
||||||
|
else readonly_ssh_user_effective_comment
|
||||||
|
}}
|
||||||
|
shell: "{{ readonly_ssh_user_shell }}"
|
||||||
|
home: "{{ readonly_ssh_user_home }}"
|
||||||
|
create_home: "{{ readonly_ssh_user_create_home }}"
|
||||||
|
password: "{{ readonly_ssh_user_password_hash if readonly_ssh_user_password_hash | length > 0 else omit }}"
|
||||||
|
password_lock: "{{ readonly_ssh_user_lock_password }}"
|
||||||
|
groups: "{{ readonly_ssh_user_groups }}"
|
||||||
|
append: "{{ readonly_ssh_user_append_groups }}"
|
||||||
|
state: "{{ readonly_ssh_user_state }}"
|
||||||
|
|
||||||
|
- name: Ensure SSH directory exists for readonly user
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ readonly_ssh_user_home }}/.ssh"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ readonly_ssh_user_name }}"
|
||||||
|
group: "{{ readonly_ssh_user_name }}"
|
||||||
|
mode: "0700"
|
||||||
|
when: readonly_ssh_user_authorized_keys | length > 0
|
||||||
|
|
||||||
|
- name: Install authorized keys for readonly user
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ readonly_ssh_user_name }}"
|
||||||
|
key: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
manage_dir: false
|
||||||
|
loop: "{{ readonly_ssh_user_authorized_keys }}"
|
||||||
|
when: readonly_ssh_user_authorized_keys | length > 0
|
||||||
|
|
||||||
|
- name: Remove readonly user from privileged groups
|
||||||
|
ansible.builtin.command: "gpasswd -d {{ readonly_ssh_user_name }} {{ item }}"
|
||||||
|
register: readonly_user_group_removal
|
||||||
|
failed_when: readonly_user_group_removal.rc not in [0, 3]
|
||||||
|
changed_when: readonly_user_group_removal.rc == 0
|
||||||
|
loop: "{{ readonly_ssh_user_restricted_groups }}"
|
||||||
|
|
||||||
|
- name: Ensure sshd drop-in directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ readonly_ssh_user_sshd_dropin_dir }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
when: readonly_ssh_user_manage_sshd
|
||||||
|
|
||||||
|
- name: Configure readonly user SSH restrictions
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ readonly_ssh_user_sshd_dropin_dir }}/{{ readonly_ssh_user_sshd_dropin_file }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
Match User {{ readonly_ssh_user_name }}
|
||||||
|
PasswordAuthentication {{ 'yes' if readonly_ssh_user_password_authentication else 'no' }}
|
||||||
|
PubkeyAuthentication {{ 'yes' if readonly_ssh_user_pubkey_authentication else 'no' }}
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
ChallengeResponseAuthentication no
|
||||||
|
PermitEmptyPasswords no
|
||||||
|
AllowTcpForwarding {{ 'yes' if readonly_ssh_user_allow_tcp_forwarding else 'no' }}
|
||||||
|
X11Forwarding {{ 'yes' if readonly_ssh_user_x11_forwarding else 'no' }}
|
||||||
|
PermitTunnel {{ 'yes' if readonly_ssh_user_permit_tunnel else 'no' }}
|
||||||
|
PermitTTY {{ 'yes' if readonly_ssh_user_permit_tty else 'no' }}
|
||||||
|
AllowAgentForwarding {{ 'yes' if readonly_ssh_user_allow_agent_forwarding else 'no' }}
|
||||||
|
{% if readonly_ssh_user_force_command | length > 0 %}
|
||||||
|
ForceCommand {{ readonly_ssh_user_force_command }}
|
||||||
|
{% endif %}
|
||||||
|
notify: reload sshd
|
||||||
|
when: readonly_ssh_user_manage_sshd
|
||||||
|
|
||||||
|
- name: Configure readonly sudo command whitelist
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ readonly_ssh_user_sudoers_file }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0440"
|
||||||
|
content: |
|
||||||
|
{{ readonly_ssh_user_name }} ALL=(root) {{ 'NOPASSWD:' if readonly_ssh_user_sudo_nopasswd else '' }} {{ readonly_ssh_user_effective_sudo_commands | join(', ') }}
|
||||||
|
notify: validate sudoers
|
||||||
|
when: readonly_ssh_user_manage_sudoers
|
||||||
@ -1,8 +1,11 @@
|
|||||||
---
|
---
|
||||||
acp_codex_service_name: codex-app-server
|
acp_codex_service_name: codex-app-server
|
||||||
acp_codex_service_user: root
|
acp_codex_runtime_user: ubuntu
|
||||||
acp_codex_service_group: root
|
acp_codex_runtime_group: "{{ acp_codex_runtime_user }}"
|
||||||
acp_codex_workdir: /root
|
acp_codex_runtime_home: "/home/{{ acp_codex_runtime_user }}"
|
||||||
|
acp_codex_service_user: "{{ acp_codex_runtime_user }}"
|
||||||
|
acp_codex_service_group: "{{ acp_codex_runtime_group }}"
|
||||||
|
acp_codex_workdir: "{{ acp_codex_runtime_home }}"
|
||||||
acp_codex_listen_host: 127.0.0.1
|
acp_codex_listen_host: 127.0.0.1
|
||||||
acp_codex_listen_port: 9001
|
acp_codex_listen_port: 9001
|
||||||
acp_codex_bridge_service_name: acp-bridge-codex
|
acp_codex_bridge_service_name: acp-bridge-codex
|
||||||
@ -22,7 +25,7 @@ acp_codex_bridge_allowed_origins:
|
|||||||
- http://localhost:*
|
- http://localhost:*
|
||||||
- http://127.0.0.1:*
|
- http://127.0.0.1:*
|
||||||
acp_codex_environment:
|
acp_codex_environment:
|
||||||
CODEX_HOME: "{{ acp_codex_workdir }}/.codex"
|
CODEX_HOME: "{{ acp_codex_runtime_home }}/.codex"
|
||||||
OPENAI_API_KEY: "{{ lookup('ansible.builtin.env', 'OPENAI_API_KEY') | default('', true) }}"
|
OPENAI_API_KEY: "{{ lookup('ansible.builtin.env', 'OPENAI_API_KEY') | default('', true) }}"
|
||||||
OPENAI_BASE_URL: "{{ lookup('ansible.builtin.env', 'OPENAI_BASE_URL') | default('', true) }}"
|
OPENAI_BASE_URL: "{{ lookup('ansible.builtin.env', 'OPENAI_BASE_URL') | default('', true) }}"
|
||||||
OPENAI_BASE_API_URL: "{{ lookup('ansible.builtin.env', 'OPENAI_BASE_API_URL') | default('', true) }}"
|
OPENAI_BASE_API_URL: "{{ lookup('ansible.builtin.env', 'OPENAI_BASE_API_URL') | default('', true) }}"
|
||||||
|
|||||||
@ -5,8 +5,8 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User={{ acp_codex_service_user }}
|
||||||
Group=root
|
Group={{ acp_codex_service_group }}
|
||||||
WorkingDirectory={{ acp_codex_workdir }}
|
WorkingDirectory={{ acp_codex_workdir }}
|
||||||
Environment=HOME={{ acp_codex_workdir }}
|
Environment=HOME={{ acp_codex_workdir }}
|
||||||
Environment=TERM=xterm-256color
|
Environment=TERM=xterm-256color
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
---
|
---
|
||||||
acp_gemini_service_name: acp-gemini-adapter
|
acp_gemini_service_name: acp-gemini-adapter
|
||||||
acp_gemini_service_user: root
|
acp_gemini_service_user: ubuntu
|
||||||
acp_gemini_service_group: root
|
acp_gemini_service_group: "{{ acp_gemini_service_user }}"
|
||||||
acp_gemini_home: /root
|
acp_gemini_home: "/home/{{ acp_gemini_service_user }}"
|
||||||
acp_gemini_workdir: /root
|
acp_gemini_workdir: "{{ acp_gemini_home }}"
|
||||||
|
acp_gemini_xdg_config_home: "{{ acp_gemini_home }}/.config"
|
||||||
|
acp_gemini_xdg_state_home: "{{ acp_gemini_home }}/.local/state"
|
||||||
|
acp_gemini_config_dir: "{{ acp_gemini_home }}/.gemini"
|
||||||
acp_gemini_binary_path: /usr/bin/gemini
|
acp_gemini_binary_path: /usr/bin/gemini
|
||||||
acp_gemini_args: --experimental-acp
|
acp_gemini_args: --experimental-acp
|
||||||
|
|
||||||
@ -23,6 +26,9 @@ acp_gemini_public_base_url: https://acp-server.svc.plus/gemini
|
|||||||
acp_gemini_manage_caddy: false
|
acp_gemini_manage_caddy: false
|
||||||
acp_gemini_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}"
|
acp_gemini_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}"
|
||||||
acp_gemini_environment:
|
acp_gemini_environment:
|
||||||
|
XDG_CONFIG_HOME: "{{ acp_gemini_xdg_config_home }}"
|
||||||
|
XDG_STATE_HOME: "{{ acp_gemini_xdg_state_home }}"
|
||||||
|
GEMINI_CONFIG_DIR: "{{ acp_gemini_config_dir }}"
|
||||||
GEMINI_ADAPTER_AUTH_TOKEN: "{{ acp_gemini_auth_token }}"
|
GEMINI_ADAPTER_AUTH_TOKEN: "{{ acp_gemini_auth_token }}"
|
||||||
ACP_GEMINI_BIN: "{{ acp_gemini_binary_path }}"
|
ACP_GEMINI_BIN: "{{ acp_gemini_binary_path }}"
|
||||||
acp_gemini_packages:
|
acp_gemini_packages:
|
||||||
|
|||||||
@ -24,8 +24,8 @@
|
|||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: "{{ acp_gemini_bridge_local_binary_path }}"
|
src: "{{ acp_gemini_bridge_local_binary_path }}"
|
||||||
dest: "{{ acp_gemini_bridge_binary_path }}"
|
dest: "{{ acp_gemini_bridge_binary_path }}"
|
||||||
owner: root
|
owner: "{{ acp_gemini_service_user }}"
|
||||||
group: root
|
group: "{{ acp_gemini_service_group }}"
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
notify: Restart gemini acp adapter
|
notify: Restart gemini acp adapter
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,8 @@ Behavior after deployment:
|
|||||||
- requests with `Authorization: Bearer $INTERNAL_SERVICE_TOKEN` are accepted
|
- requests with `Authorization: Bearer $INTERNAL_SERVICE_TOKEN` are accepted
|
||||||
- this playbook only defines and validates the shared ingress token path
|
- this playbook only defines and validates the shared ingress token path
|
||||||
- provider-specific authentication and ACP method compatibility are intentionally left to the individual runtimes
|
- provider-specific authentication and ACP method compatibility are intentionally left to the individual runtimes
|
||||||
|
- the Codex runtime user is a role variable and defaults to `ubuntu`, so it can be changed from inventory if needed
|
||||||
|
- Gemini adapter is now also aligned to `ubuntu` home paths so it can reuse `/home/ubuntu/.gemini/oauth_creds.json`
|
||||||
|
|
||||||
## Public Endpoints
|
## Public Endpoints
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user