diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b5aa7f6..b34cf22 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,6 +14,7 @@ jobs: matrix: features: - ohmyposh + - microsoft-security-devops-cli baseImage: - debian:latest - ubuntu:latest @@ -34,6 +35,7 @@ jobs: matrix: features: - ohmyposh + - microsoft-security-devops-cli steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 753a14f..a4cd49d 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,40 @@ To use a custom theme file from your host machine, add a mount in your `devconta The feature creates a placeholder file at `~/.ohmyposh.json` during installation. If you mount a custom theme to this location, it will be used automatically. Otherwise, the built-in theme specified in the options will be used. +### Microsoft Security DevOps CLI + +Installs [Microsoft Security DevOps CLI](https://aka.ms/msdodocs) (`guardian` command) for running security analysis tools without requiring .NET installation. + +**Usage:** + +```json +{ + "features": { + "ghcr.io/rosstaco/devcontainer-features/microsoft-security-devops-cli:1": { + "version": "latest" + } + } +} +``` + +**Options:** +- `version` - Version to install (default: "latest"). Use "latest" or a specific version like "0.215.0" +- `installPath` - Installation directory (default: "/usr/local/bin/guardian") + +**Supported Architectures:** +- linux-x64 (x86_64) +- linux-arm64 (aarch64) + +**Running Guardian:** + +After installation, the `guardian` command is available in your PATH. To initialize guardian in your repository: + +```bash +guardian init --force +``` + +Note: `guardian init` requires a git repository, so it must be run manually after the container starts (not during feature installation). + ## Publishing This repository uses a **GitHub Action** [workflow](.github/workflows/release.yaml) that publishes each Feature to GHCR (GitHub Container Registry). diff --git a/docs/microsoft-security-devops-cli-plan.md b/docs/microsoft-security-devops-cli-plan.md new file mode 100644 index 0000000..51f0da9 --- /dev/null +++ b/docs/microsoft-security-devops-cli-plan.md @@ -0,0 +1,202 @@ +--- +description: Implementation plan for adding Microsoft Security DevOps CLI (guardian) feature to devcontainer-features project +--- + +# Microsoft Security DevOps CLI Feature Implementation Plan + +## Overview + +Add a new devcontainer feature to install Microsoft Security DevOps CLI (`guardian` command) by downloading the architecture-specific nuget package, extracting binaries to a common location, and adding to PATH. + +## Verified Information + +- **Nuget API behavior**: `https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.linux-x64` without version parameter redirects to latest version (0.215.0 as of test) +- **Available architectures**: Only `linux-x64` and `linux-arm64` (no musl, arm, or other variants) +- **Command name**: `guardian` (Microsoft.Guardian.Cli binary name) +- **All binaries in tools/**: The nuget package contains all binaries in the `tools/` directory +- **Init command**: `guardian init --force` requires a git repository, so cannot be run during feature installation + +## Implementation Steps + +### 1. Create Feature Structure + +Create `src/microsoft-security-devops-cli/` directory with: + +#### `devcontainer-feature.json` +```json +{ + "id": "microsoft-security-devops-cli", + "version": "1.0.0", + "name": "Microsoft Security DevOps CLI", + "description": "Installs Microsoft Security DevOps CLI (guardian) for running security analysis tools", + "documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/microsoft-security-devops-cli", + "options": { + "version": { + "type": "string", + "default": "latest", + "description": "Version of Microsoft Security DevOps CLI to install. Use 'latest' or a specific version like '0.215.0'" + }, + "installPath": { + "type": "string", + "default": "/usr/local/bin/guardian", + "description": "Directory where the guardian binaries will be installed" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} +``` + +#### `install.sh` +Key requirements: +- Use `set -e` for error handling +- Detect architecture: `x86_64` → `linux-x64`, `aarch64|arm64` → `linux-arm64`, else error +- Read options: `VERSION="${VERSION:-latest}"`, `INSTALL_PATH="${INSTALLPATH:-/usr/local/bin/guardian}"` +- Build download URL: + - For "latest": `https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.${ARCH}` + - For specific version: `https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.${ARCH}/${VERSION}` +- Download to temp file (e.g., `/tmp/guardian-cli.nupkg`) +- Unzip to temp directory (e.g., `/tmp/guardian-extract`) +- Copy all files from `tools/*` to `$INSTALL_PATH` +- Set executable permissions: `chmod +x $INSTALL_PATH/*` or individually for binaries +- Verify installation: `guardian --version` +- Output completion message with hint about `guardian init --force` command +- Use colored output (GREEN, RED, YELLOW, NC) like ohmyposh pattern + +### 2. Create Test Structure + +Create `test/microsoft-security-devops-cli/` directory with: + +#### `scenarios.json` +```json +{ + "version": { + "image": "ubuntu:latest", + "features": { + "microsoft-security-devops-cli": { + "version": "0.215.0" + } + } + }, + "custom-install-path": { + "image": "ubuntu:latest", + "features": { + "microsoft-security-devops-cli": { + "installPath": "/usr/bin" + } + } + } +} +``` + +#### `test.sh` +Tests to implement: +```bash +#!/bin/bash +set -e +source dev-container-features-test-lib + +check "guardian is installed" guardian --version +check "guardian is executable" which guardian +check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian +check "guardian can show help" guardian --help + +reportResults +``` + +#### Additional test files (optional): +- `version.sh` - Test specific version installation +- `custom-install-path.sh` - Test custom installation path + +### 3. Update Project Documentation + +Update `README.md` to add new feature section after Oh My Posh: + +```markdown +### Microsoft Security DevOps CLI + +Installs [Microsoft Security DevOps CLI](https://aka.ms/msdodocs) (`guardian` command) for running security analysis tools without requiring .NET installation. + +**Usage:** + +```json +{ + "features": { + "ghcr.io/rosstaco/devcontainer-features/microsoft-security-devops-cli:1": { + "version": "latest", + "installPath": "/usr/local/bin" + } + } +} +``` + +**Options:** +- `version` - Version to install (default: "latest"). Use "latest" or a specific version like "0.215.0" +- `installPath` - Installation directory (default: "/usr/local/bin") + +**Supported Architectures:** +- linux-x64 (x86_64) +- linux-arm64 (aarch64) + +**Running Guardian:** + +After installation, the `guardian` command is available in your PATH. To initialize guardian in your repository: + +```bash +guardian init --force +``` + +Note: `guardian init` requires a git repository, so it must be run manually after the container starts (not during feature installation). +``` + +### 4. Update Justfile (Optional) + +Add build command for new feature: + +```justfile +# Build Microsoft Security DevOps CLI feature +build-microsoft-security-devops-cli: + just build-feature microsoft-security-devops-cli + +# Build all features +build-all: + just build-ohmyposh + just build-microsoft-security-devops-cli +``` + +## Key Design Decisions + +1. **No curl/unzip checks**: Rely on `common-utils` feature via `installsAfter` to ensure dependencies are available +2. **Simple version handling**: "latest" uses API without version parameter (auto-redirects), specific versions use full URL path +3. **Copy entire tools/ directory**: Install all binaries from nuget package's tools folder to support any dependencies +4. **No auto-init**: Don't run `guardian init --force` automatically as it requires a git repository +5. **Minimal options**: Only version and installPath, keeping it simple like the user requested +6. **Error on unsupported arch**: Clear error messages for architectures that don't have nuget packages + +## Installation Flow + +``` +1. Feature detects architecture (x86_64 or aarch64) +2. Maps to nuget package variant (linux-x64 or linux-arm64) +3. Downloads .nupkg file from nuget.org API +4. Extracts to temporary directory +5. Copies tools/* binaries to install path +6. Sets executable permissions +7. Verifies guardian --version works +8. Outputs completion message with init instructions +``` + +## Testing Strategy + +1. **Default test**: Install latest version to /usr/local/bin on ubuntu:latest +2. **Version test**: Install specific version (0.215.0) +3. **Custom path test**: Install to /usr/bin instead of default +4. **Verification**: Each test confirms binary exists, is executable, in PATH, and runs successfully + +## Future Enhancements (Not in Initial Implementation) + +- Support for macOS architectures (osx-x64, osx-arm64) if needed +- Automatic guardian init if git repo detected +- Configuration file support +- Tool-specific version pinning diff --git a/justfile b/justfile index 2d58032..674c4e9 100644 --- a/justfile +++ b/justfile @@ -13,9 +13,14 @@ build-feature feature: build-ohmyposh: just build-feature ohmyposh +# Build Microsoft Security DevOps CLI feature +build-microsoft-security-devops-cli: + just build-feature microsoft-security-devops-cli + # Build all features build-all: just build-ohmyposh + just build-microsoft-security-devops-cli # Clean copied features clean: diff --git a/src/microsoft-security-devops-cli/devcontainer-feature.json b/src/microsoft-security-devops-cli/devcontainer-feature.json new file mode 100644 index 0000000..64ac3f9 --- /dev/null +++ b/src/microsoft-security-devops-cli/devcontainer-feature.json @@ -0,0 +1,22 @@ +{ + "id": "microsoft-security-devops-cli", + "version": "1.0.0", + "name": "Microsoft Security DevOps CLI", + "description": "Installs Microsoft Security DevOps CLI (guardian) for running security analysis tools", + "documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/microsoft-security-devops-cli", + "options": { + "version": { + "type": "string", + "default": "latest", + "description": "Version of Microsoft Security DevOps CLI to install. Use 'latest' or a specific version like '0.215.0'" + }, + "installPath": { + "type": "string", + "default": "/usr/local/bin/guardian", + "description": "Directory where the guardian binaries will be installed" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/src/microsoft-security-devops-cli/install.sh b/src/microsoft-security-devops-cli/install.sh new file mode 100644 index 0000000..2a7e3d2 --- /dev/null +++ b/src/microsoft-security-devops-cli/install.sh @@ -0,0 +1,168 @@ +#!/bin/bash +set -e + +# Microsoft Security DevOps CLI installation script for devcontainer features +# https://aka.ms/msdodocs + +VERSION="${VERSION:-latest}" +INSTALL_PATH="${INSTALLPATH:-/usr/local/bin/guardian}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}Installing Microsoft Security DevOps CLI...${NC}" + +# Ensure curl is available +if ! command -v curl &> /dev/null; then + echo "Installing curl..." + export DEBIAN_FRONTEND=noninteractive + if command -v apt-get &> /dev/null; then + apt-get update && apt-get install -y curl + elif command -v apk &> /dev/null; then + apk add --no-cache curl + elif command -v yum &> /dev/null; then + yum install -y curl + else + echo -e "${RED}Could not install curl. Please install it manually.${NC}" + exit 1 + fi +fi + +if ! command -v unzip &> /dev/null; then + echo "Installing unzip..." + export DEBIAN_FRONTEND=noninteractive + if command -v apt-get &> /dev/null; then + apt-get update && apt-get install -y unzip + elif command -v apk &> /dev/null; then + apk add --no-cache unzip + elif command -v yum &> /dev/null; then + yum install -y unzip + else + echo -e "${RED}Could not install unzip. Please install it manually.${NC}" + exit 1 + fi +fi + + +# Detect architecture +ARCH=$(uname -m) +case $ARCH in + x86_64) + ARCH="linux-x64" + ;; + aarch64|arm64) + ARCH="linux-arm64" + ;; + *) + echo -e "${RED}Unsupported architecture: $ARCH${NC}" + echo -e "${RED}Only x86_64 (linux-x64) and aarch64 (linux-arm64) are supported.${NC}" + exit 1 + ;; +esac + +echo "Detected architecture: $ARCH" + +# Build download URL +if [ "$VERSION" = "latest" ]; then + echo "Fetching latest version..." + DOWNLOAD_URL="https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.${ARCH}" +else + echo "Using version: $VERSION" + DOWNLOAD_URL="https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.${ARCH}/${VERSION}" +fi + +echo "Downloading from: $DOWNLOAD_URL" + +# Download the package +TEMP_FILE="/tmp/guardian-cli.nupkg" +if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_FILE"; then + echo -e "${RED}Failed to download Microsoft Security DevOps CLI${NC}" + exit 1 +fi + +# Extract the package +TEMP_DIR="/tmp/guardian-extract" +mkdir -p "$TEMP_DIR" +if ! unzip -q -o "$TEMP_FILE" -d "$TEMP_DIR"; then + echo -e "${RED}Failed to extract package${NC}" + rm -rf "$TEMP_DIR" "$TEMP_FILE" + exit 1 +fi + +# Create install directory if it doesn't exist +mkdir -p "$INSTALL_PATH" + +# Copy all binaries from tools directory +if [ -d "$TEMP_DIR/tools" ]; then + echo "Installing binaries to $INSTALL_PATH..." + + # Copy entire tools directory to preserve .NET runtime structure + cp -r "$TEMP_DIR/tools/"* "$INSTALL_PATH/" + + # Set executable permissions recursively + find "$INSTALL_PATH" -type f -exec chmod +x {} \; 2>/dev/null || true +else + echo -e "${RED}tools directory not found in package${NC}" + rm -rf "$TEMP_DIR" "$TEMP_FILE" + exit 1 +fi + +# Add install path to PATH if not already present +if [[ ":$PATH:" != *":$INSTALL_PATH:"* ]]; then + echo "Adding $INSTALL_PATH to PATH..." + + # Add to /etc/environment for system-wide PATH + if [ -f /etc/environment ]; then + # Check if PATH exists in /etc/environment + if grep -q "^PATH=" /etc/environment; then + # Append to existing PATH + sed -i "s|^PATH=\"\(.*\)\"|PATH=\"\1:$INSTALL_PATH\"|" /etc/environment + else + # Add new PATH entry + echo "PATH=\"$PATH:$INSTALL_PATH\"" >> /etc/environment + fi + fi + + # Add to common shell rc files + for rc_file in /etc/bash.bashrc /etc/zsh/zshrc; do + if [ -f "$rc_file" ]; then + if ! grep -q "$INSTALL_PATH" "$rc_file"; then + echo "export PATH=\"\$PATH:$INSTALL_PATH\"" >> "$rc_file" + fi + fi + done + + # Update current session PATH + export PATH="$PATH:$INSTALL_PATH" +fi + +# Cleanup +rm -rf "$TEMP_DIR" "$TEMP_FILE" + +echo -e "${GREEN}Microsoft Security DevOps CLI installed to $INSTALL_PATH${NC}" + +# Verify installation +if ! command -v guardian &> /dev/null; then + echo -e "${YELLOW}Warning: guardian command not found in PATH${NC}" + echo -e "${YELLOW}You may need to restart your shell or source your shell configuration${NC}" +else + echo -e "${GREEN}Guardian installation verified successfully${NC}" +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Installation complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo "The 'guardian' command is now available." +echo "" +echo "To initialize guardian in your repository, run:" +echo "" +echo " guardian init --force" +echo "" +echo "Note: guardian init requires a git repository." +echo "" +echo "For more information: https://aka.ms/msdodocs" diff --git a/test/microsoft-security-devops-cli/custom-install-path.sh b/test/microsoft-security-devops-cli/custom-install-path.sh new file mode 100644 index 0000000..4a35461 --- /dev/null +++ b/test/microsoft-security-devops-cli/custom-install-path.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# This test verifies custom install path + +set -e + +source dev-container-features-test-lib + +check "guardian installed in custom path" test -x /usr/bin/guardian/guardian + +check "guardian is accessible" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true" + +check "guardian is in PATH" which guardian + +reportResults diff --git a/test/microsoft-security-devops-cli/scenarios.json b/test/microsoft-security-devops-cli/scenarios.json new file mode 100644 index 0000000..8a627b0 --- /dev/null +++ b/test/microsoft-security-devops-cli/scenarios.json @@ -0,0 +1,18 @@ +{ + "version": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "microsoft-security-devops-cli": { + "version": "0.215.0" + } + } + }, + "custom-install-path": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "microsoft-security-devops-cli": { + "installPath": "/usr/bin/guardian" + } + } + } +} diff --git a/test/microsoft-security-devops-cli/test.sh b/test/microsoft-security-devops-cli/test.sh new file mode 100644 index 0000000..38da72b --- /dev/null +++ b/test/microsoft-security-devops-cli/test.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This test file will be executed against an auto-generated devcontainer.json that +# includes the 'microsoft-security-devops-cli' Feature with no options. +# +# Thus, the value of all options will fall back to the default value in the +# Feature's 'devcontainer-feature.json'. +# +# These scripts are run as 'root' by default. Although that can be changed +# with the '--remote-user' flag. + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +# Feature-specific tests +check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true" + +check "guardian is executable" which guardian + +check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian + +check "guardian can show version" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+' || true" + +# Report results +reportResults diff --git a/test/microsoft-security-devops-cli/version.sh b/test/microsoft-security-devops-cli/version.sh new file mode 100644 index 0000000..3d596a4 --- /dev/null +++ b/test/microsoft-security-devops-cli/version.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# This test verifies that a specific version can be installed + +set -e + +source dev-container-features-test-lib + +check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true" + +check "guardian is executable" which guardian + +check "guardian version command works" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+' || true" + +reportResults