Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .env.example

This file was deleted.

1 change: 0 additions & 1 deletion .envrc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
source .venv/bin/activate
dotenv_if_exists .env
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ results/
/.vscode/
/.env
/.env.*
!/.env.example
/.tox/
/.eggs/
.poetry_cache/
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ This repository ships skill files that document how to interact with the CLI and
source cisco_sccfm_scripts/activate.sh

# Configure credentials once
sccfm-cli configure --region us --api-token <YOUR_TOKEN>
sccfm-cli configure --region us # securely prompts for the token

# Check connectivity
sccfm-cli status
Expand All @@ -58,19 +58,18 @@ sccfm-cli status
sccfm-cli inventory devices list --format table

# Interactive developer menu (test, lint, format, build collection, etc.)
devkit
sccfm-cli-interactive
```

## Required environment variables
## Credential configuration

Copy `.env.example` to `.env` and fill in your values (loaded automatically by direnv):
The only SCCFM token configuration source is the named profile store:

```bash
export SCCFM_REGION=us # int | us | eu | apj | au | uae | in | ci
export SCCFM_API_TOKEN="..." # from SCCFM UI > Settings > API Tokens
sccfm-cli --profile default configure --region us
```

Credentials are also stored under `~/.sccfm-cli/` after running `sccfm-cli configure`. Override the path with `--config-path` or `SCCFM_CONFIG`.
Profiles are stored in `~/.sccfm-cli/config.json` with owner-only permissions. Override the path with `--config-path` or `SCCFM_CONFIG`. Do not configure SCCFM tokens through `.env`, inline Ansible parameters, or Ansible Vault. Vault remains appropriate for Ansible-specific device secrets.

## Testing instructions

Expand Down Expand Up @@ -101,8 +100,8 @@ No MCP servers are currently configured for this project. Skill files under `ski
# Build and install locally
build-ansible-collection

# Set up tokens and vault
devkit # select "change-tokens"
# Configure or select profiles interactively
sccfm-cli-interactive

# Verify inventory plugin
ansible-inventory -i sccfm-ansible/examples/inventory.sccfm.yml --graph
Expand All @@ -122,7 +121,7 @@ Add `sccfm-ansible` to `ANSIBLE_COLLECTIONS_PATH` so IDE/mypy resolves `ansible_
git cz # or: ./cisco_sccfm_scripts/cz.sh commit
```
CI will fail on non-compliant commit messages.
- **Security**: Never commit real credentials, tokens, or secrets. Use placeholders and document required env vars. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
- **Security**: Never commit real credentials, tokens, or secrets. Use placeholders and document the canonical profile flow. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
- New commands go in `cisco_sccfm_cli/commands/` as a `BaseCommand` subclass, registered in `cisco_sccfm_cli/cli.py`.
- New SDK integrations go in `cisco_sccfm_core/services/`.
- Every behavior change must be accompanied by tests.
Expand All @@ -138,5 +137,5 @@ Add `sccfm-ansible` to `ANSIBLE_COLLECTIONS_PATH` so IDE/mypy resolves `ansible_
#
# SPDX-License-Identifier: Apache-2.0
```
- **Secrets**: never read or commit `.env`, `.env.*`, `.vault_pass`, or real `vault.yml` files — use the `*.example` templates. Keep tracked `.envrc` files secret-free. `gitleaks` and `detect-private-key` block secrets in pre-commit.
- **Secrets**: never read or commit `.vault_pass`, real `vault.yml` files, or SCCFM profile files. Use the `*.example` vault templates for Ansible-specific secrets. Keep tracked `.envrc` files secret-free. `gitleaks` and `detect-private-key` block secrets in pre-commit.
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contribution guide.
7 changes: 3 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,12 @@ reserve breaking changes until the next major version release.
direnv allow
```

3. Set up your SCCFM credentials:
3. Set up your SCCFM profile:
```bash
cp .env.example .env
# Edit .env with your API token
sccfm-cli configure --region us # securely prompts for the token
```

Now whenever you `cd` into the project, the virtualenv activates and env vars load automatically.
Now whenever you `cd` into the project, the virtualenv activates automatically.

## Committing Changes

Expand Down
26 changes: 11 additions & 15 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,14 @@ After sourcing, tab completion will work for all `sccfm` commands and options.
The same PyPI package exposes the typed `cisco_sccfm_core` library for Python automation:

```python
from dataclasses import dataclass

from cisco_sccfm_core import InventoryService
from cisco_sccfm_core.services import ProfileService

profile = ProfileService().load("default")
if profile is None:
raise RuntimeError("Configure the default profile with sccfm-cli configure")

@dataclass(frozen=True)
class Config:
region: str
api_token: str


inventory = InventoryService(Config(region="us", api_token="..."))
inventory = InventoryService(profile)
devices = inventory.get_devices(limit=10, offset=0, query=None)
```

Expand Down Expand Up @@ -174,19 +170,19 @@ ansible-galaxy collection list | grep cisco.sccfm

### Try out examples

The fastest way to get going is to use the interactive devkit menu:
The fastest way to get going is to use the interactive CLI menu:

```bash
devkit
# select "change-tokens" from the menu
sccfm-cli-interactive
# select "configure-profile" from the menu
```

Or run the token setup directly:
Or configure the canonical profile directly:

```bash
change-tokens
sccfm-cli configure --region us # securely prompts for the token
```

This will prompt for your region, API token, and vault password, then create all the required files (.env, vars.yml, vault.yml).
The profile is shared by `sccfm-cli`, `sccfm-cli-interactive`, and the `cisco.sccfm` Ansible collection. Ansible Vault remains available separately for managed-device passwords and other playbook-specific secrets.

See the [Trying out examples](sccfm-ansible/README.md#trying-out-examples) section in the Ansible collection README for the full walkthrough including how to run playbooks.
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Python scripts, and collection can reuse the same SDK integrations.
cisco_sccfm_scripts/setup_environment.sh # installs pyenv, Python 3.12.4, Poetry deps
source cisco_sccfm_scripts/activate.sh # activates the project virtualenv
sccfm-cli --help # the main SCCFM CLI
devkit # interactive developer toolkit menu
sccfm-cli-interactive # interactive CLI and developer workflow menu
```

`setup_environment.sh` keeps everything local to the repository: pyenv provides Python 3.12.4, `.venv/` hosts the runtime, and Poetry installs the project plus dev dependencies.

## Commands

- `sccfm-cli configure [--region REGION] [--api-token TOKEN] [--config-path PATH]`: Captures the SCCFM region (`int`, `us`, `eu`, `apj`, `au`, `uae`, `in`, or `ci`) plus an API token (see the [auth guide](https://developer.cisco.com/docs/cisco-security-cloud-control-firewall-manager/authentication/)) and stores it under `~/.sccfm-cli/` (override with `--config-path` or `SCCFM_CONFIG`).
- `sccfm-cli configure [--region REGION] [--api-token TOKEN] [--config-path PATH]`: Captures the SCCFM region (`int`, `us`, `eu`, `apj`, `au`, `uae`, `in`, or `ci`) plus an API token (see the [auth guide](https://developer.cisco.com/docs/cisco-security-cloud-control-firewall-manager/authentication/)) in the canonical profile store at `~/.sccfm-cli/config.json`. The directory is restricted to the current user (`0700`) and the file to owner read/write (`0600`). Override the path with `--config-path` or `SCCFM_CONFIG`.
- `sccfm-cli status [--config-path PATH]`: Shows the current profile plus SCCFM connectivity health using Rich tables.
- `sccfm-cli inventory devices list [--limit N] [--offset N] [--query TEXT] [--format table|json]`: Lists device inventory with pagination and optional name filtering.
- `sccfm-cli inventory manager list [--limit N] [--offset N] [--query TEXT] [--format table|json]`: Lists manager inventory with the same filters.
Expand Down Expand Up @@ -69,18 +69,14 @@ Installing the `cisco-sccfm-devkit` package also exposes `cisco_sccfm_core`, a t
Python automation library built on top of the generated `scc-firewall-manager-sdk`.

```python
from dataclasses import dataclass

from cisco_sccfm_core import InventoryService
from cisco_sccfm_core.services import ProfileService

profile = ProfileService().load("default")
if profile is None:
raise RuntimeError("Configure the default profile with sccfm-cli configure")

@dataclass(frozen=True)
class Config:
region: str
api_token: str


inventory = InventoryService(Config(region="us", api_token="..."))
inventory = InventoryService(profile)
devices = inventory.get_devices(limit=10, offset=0, query=None)
```

Expand All @@ -91,27 +87,32 @@ The package root exports the supported public service classes and response model

- macOS: `brew install ansible` (this includes `ansible-galaxy`; verify with `ansible-galaxy --version`).
- Build and install the collection locally: `build-ansible-collection`.
- Set up tokens interactively: `devkit` and select **change-tokens** (saves your API token, creates `.env`, `.vault_pass`, encrypts `group_vars/all/vault.yml`, and sets the region).
- Configure profiles interactively: run `sccfm-cli-interactive` and select **configure-profile**.
- For IDEs/mypy, add `sccfm-ansible` to `ANSIBLE_COLLECTIONS_PATH` (or mark it as a source root) so imports under `ansible_collections.cisco.sccfm` resolve without installing.
- Configure SCCFM region (`int`, `us`, `eu`, `apj`, `au`, `uae`, `in`, or `ci`) plus `SCCFM_API_TOKEN`; you can set them via env vars or inline (i.e., write the values directly in the inventory file—useful for local dev, but prefer env vars or Ansible Vault for anything shared).
- Ansible modules and inventory select the same named SCCFM profile; they do not duplicate its region or API token in environment variables, playbooks, or Ansible Vault.
- Keep Ansible Vault for playbook-specific secrets such as managed-device passwords.
- Point Ansible at an inventory file that uses the plugin, e.g. `ansible-inventory -i sccfm-ansible/examples/inventory.sccfm.yml --graph`.
- A starter playbook is in `sccfm-ansible/examples/show_devices.yml`; it runs against the SCCFM devices discovered by the inventory plugin.
- Generated Ansible reference docs can be previewed locally with `generate-ansible-docs`; see [docs/README.md](docs/README.md) for details.

## Development

All common development tasks are available through the interactive `devkit` menu:
All common development tasks are available through the interactive CLI menu:

```bash
source cisco_sccfm_scripts/activate.sh
devkit
sccfm-cli-interactive
```

This presents an interactive selector with the following tasks:

| Task | Description |
|------|-------------|
| **change-tokens** | Set up SCCFM API tokens, .env, and Ansible Vault |
| **configure-profile** | Create or replace a canonical SCCFM profile |
| **manage-profiles** | Update or remove SCCFM profiles |
| **import-legacy-vault** | Copy profiles from the former vault token store without modifying it |
| **run-cli** | Discover and run an `sccfm-cli` command interactively |
| **run-ansible** | Select and run an example playbook |
| **build-collection** | Build the cisco.sccfm Ansible collection tarball |
| **generate-ansible-docs** | Generate Ansible reference docs from ansible-doc output |
| **generate-cli-docs** | Generate CLI reference docs from Click help output |
Expand Down
2 changes: 2 additions & 0 deletions cisco_sccfm_cli/commands/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def build_params(self) -> Sequence[click.Parameter]:
help="API token for the chosen region",
group=credential_group,
required=True,
prompt="API token",
hide_input=True,
),
]

Expand Down
22 changes: 22 additions & 0 deletions cisco_sccfm_cli/commands/tests/test_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,25 @@ def test_should_normalize_legacy_region_aliases(cli_runner: CliRunner, config_pa
stored = ConfigService(path=config_path).load("lab")
assert stored is not None
assert stored.region == "au"


def test_should_prompt_for_token_without_echoing_it(
cli_runner: CliRunner, config_path: Path
) -> None:
result = cli_runner.invoke(
cli,
[
"configure",
"--region",
"us",
"--config-path",
str(config_path),
],
input="prompted-secret\n",
)

assert result.exit_code == 0
assert "prompted-secret" not in result.output
stored = ConfigService(path=config_path).load("default")
assert stored is not None
assert stored.api_token == "prompted-secret"
4 changes: 4 additions & 0 deletions cisco_sccfm_cli/commands/tests/test_sccfm_cli_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def test_cisco_sccfm_cli_skill_should_cover_schema_driven_operation() -> None:
for fragment in expected_fragments:
assert fragment in body

assert "sccfm-cli-interactive" in body
assert "SCCFM_API_TOKEN" not in body
assert "SCCFM_REGION" not in body


def test_cisco_sccfm_cli_skill_should_reference_fields_emitted_by_schema(
cli_runner: CliRunner,
Expand Down
19 changes: 16 additions & 3 deletions cisco_sccfm_cli/e2e/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents

- [sccfm-cli E2E Integration Tests](#sccfm-cli-e2e-integration-tests)
- [Structure](#structure)
- [Why This Shape](#why-this-shape)
- [Prerequisites](#prerequisites)
- [Running](#running)
- [Opt-in upgrade phases](#opt-in-upgrade-phases)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# sccfm-cli E2E Integration Tests

Tenant-backed integration tests for the `sccfm-cli` binary. The suite mirrors `sccfm-ansible/e2e/` 1:1 so the same scenarios are exercised through both surfaces.
Expand All @@ -22,17 +35,17 @@ Tenant-backed integration tests for the `sccfm-cli` binary. The suite mirrors `
- Jenkins gets one test case per lifecycle phase instead of one large pass/fail result.
- Phases shell out to the installed `sccfm-cli` entrypoint, so the suite exercises argv parsing, exit codes, and stdout/stderr the way real users see them — exactly the contract that unit tests with `CliRunner` skip.
- Test data per suite lives in one file (`phases/test_data.py`), reducing drift between create / verify / update / delete phases.
- Credentials reuse the Ansible suite's vault (`cisco_sccfm_scripts/setup_tokens.py`). One CI bootstrap, two test surfaces.
- Credentials use the same canonical named profile as the CLI and Ansible collection.

## Prerequisites

1. Run the credential bootstrap once:

```
poetry run change-tokens
sccfm-cli --profile default configure --region ci
```

This creates `sccfm-ansible/examples/.vault_pass` and an encrypted `vault.yml`.
Create `sccfm-ansible/examples/.vault_pass` and encrypted `vault.yml` separately only when the test workflow needs Ansible-specific device secrets.

2. Install dev dependencies so `ansible-vault` is available for the runner to decode the vault:

Expand Down
Loading
Loading