container security

Securing Container Registry with Harbor

Harbor is an open-source container registry that provides security features including vulnerability scanning (integrated Trivy), image signing (Notary/Cosign), RBAC, content trust policies, replication, and audit logging. Securing Harbor involves configuring these features to enforce image provenance, prevent vulnerable image deployment, and maintain registry access control.

containersdockerharborkubernetesregistrysecurity
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Harbor is an open-source container registry that provides security features including vulnerability scanning (integrated Trivy), image signing (Notary/Cosign), RBAC, content trust policies, replication, and audit logging. Securing Harbor involves configuring these features to enforce image provenance, prevent vulnerable image deployment, and maintain registry access control.

When to Use

  • When deploying or configuring securing container registry with harbor capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Harbor 2.10+ installed (Helm or Docker Compose)
  • TLS certificates for HTTPS
  • Trivy scanner integration
  • OIDC/LDAP for authentication
  • Kubernetes cluster (for deployment target)

Workflow

Step 1: Install Harbor with Security Configuration

# harbor-values.yaml for Helm deployment
expose:
  type: ingress
  tls:
    enabled: true
    certSource: secret
    secret:
      secretName: harbor-tls
      notarySecretName: harbor-tls
  ingress:
    hosts:
      core: harbor.example.com
      notary: notary.example.com
 
externalURL: https://harbor.example.com
 
persistence:
  enabled: true
  resourcePolicy: "keep"
 
harborAdminPassword: "<strong-password>"
 
trivy:
  enabled: true
  gitHubToken: "<github-token>"
  severity: "CRITICAL,HIGH,MEDIUM"
  autoScan: true
 
notary:
  enabled: true
 
core:
  secretKey: "<32-char-secret>"
 
database:
  type: external
  external:
    host: postgres.example.com
    port: "5432"
    username: harbor
    password: "<db-password>"
    sslmode: require
helm repo add harbor https://helm.getharbor.io
helm install harbor harbor/harbor -f harbor-values.yaml -n harbor --create-namespace

Step 2: Configure Vulnerability Scanning Policies

# Enable auto-scan on push (via Harbor API)
curl -k -X PUT "https://harbor.example.com/api/v2.0/projects/myproject" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "metadata": {
      "auto_scan": "true",
      "severity": "critical",
      "prevent_vul": "true",
      "reuse_sys_cve_allowlist": "true"
    }
  }'

Step 3: Configure Content Trust

# Enable content trust at project level
curl -k -X PUT "https://harbor.example.com/api/v2.0/projects/myproject" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "metadata": {
      "enable_content_trust": "true",
      "enable_content_trust_cosign": "true"
    }
  }'
 
# Sign image with Cosign
cosign sign --key cosign.key harbor.example.com/myproject/myapp:v1.0.0
 
# Verify signature
cosign verify --key cosign.pub harbor.example.com/myproject/myapp:v1.0.0

Step 4: Configure RBAC and Project Isolation

# Create project with private visibility
curl -k -X POST "https://harbor.example.com/api/v2.0/projects" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "metadata": {
      "public": "false",
      "auto_scan": "true",
      "prevent_vul": "true",
      "severity": "high"
    }
  }'
 
# Harbor roles: ProjectAdmin, Maintainer, Developer, Guest, LimitedGuest
# Add member with specific role
curl -k -X POST "https://harbor.example.com/api/v2.0/projects/production/members" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "role_id": 3,
    "member_user": {"username": "developer1"}
  }'

Step 5: Configure Immutable Tags and Retention

# Create tag immutability rule (prevent overwriting release tags)
curl -k -X POST "https://harbor.example.com/api/v2.0/projects/production/immutabletagrules" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_filter": "v*",
    "scope_selectors": {
      "repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]
    }
  }'
 
# Configure retention policy (keep last 10 tags, delete untagged after 7 days)
curl -k -X POST "https://harbor.example.com/api/v2.0/retentions" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "algorithm": "or",
    "rules": [
      {
        "action": "retain",
        "template": "latestPushedK",
        "params": {"latestPushedK": 10},
        "tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "**"}],
        "scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
      }
    ],
    "trigger": {"kind": "Schedule", "settings": {"cron": "0 0 * * *"}}
  }'

Step 6: OIDC Authentication Integration

# Harbor configuration for OIDC
auth_mode: oidc_auth
oidc_name: "Okta"
oidc_endpoint: "https://company.okta.com/oauth2/default"
oidc_client_id: "harbor-client-id"
oidc_client_secret: "harbor-client-secret"
oidc_groups_claim: "groups"
oidc_admin_group: "harbor-admins"
oidc_scope: "openid,profile,email,groups"
oidc_verify_cert: true
oidc_auto_onboard: true

Validation Commands

# Test vulnerability prevention (should block pull of vulnerable image)
docker pull harbor.example.com/production/vulnerable-app:latest
# Expected: Error - image blocked due to vulnerabilities
 
# Verify content trust enforcement
DOCKER_CONTENT_TRUST=0 docker push harbor.example.com/production/unsigned:latest
# Expected: Push rejected due to content trust policy
 
# Check scan results via API
curl -k "https://harbor.example.com/api/v2.0/projects/production/repositories/myapp/artifacts/v1.0.0/additions/vulnerabilities" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)"
 
# Audit log check
curl -k "https://harbor.example.com/api/v2.0/audit-logs?page=1&page_size=10" \
  -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)"

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.9 KB

API Reference: Securing Container Registry with Harbor

Harbor REST API v2.0

Method Endpoint Description
GET /api/v2.0/projects List all projects
PUT /api/v2.0/projects/{name} Update project settings
GET /api/v2.0/configurations Get system config
PUT /api/v2.0/configurations Update system config
GET /api/v2.0/projects/{name}/members List project members
POST /api/v2.0/projects/{name}/members Add member
GET /api/v2.0/projects/{name}/immutabletagrules List tag rules
GET /api/v2.0/audit-logs Get audit logs
GET /api/v2.0/projects/{name}/repositories/{repo}/artifacts/{ref}/additions/vulnerabilities Get scan results

Harbor Roles

Role ID Name Permissions
1 ProjectAdmin Full project control
2 Maintainer Push/pull/scan/sign
3 Developer Push and pull images
4 Guest Pull images only
5 LimitedGuest Pull specific repos

Security Metadata Fields

Field Values Description
auto_scan true/false Scan images on push
prevent_vul true/false Block vulnerable images
severity critical/high/medium Block threshold
enable_content_trust true/false Notary signing
enable_content_trust_cosign true/false Cosign verification
public true/false Public project access

Python Libraries

Library Version Purpose
requests >=2.28 Harbor REST API calls
json stdlib Parse API responses

References

standards.md1.1 KB

Standards Reference - Harbor Container Registry Security

NIST SP 800-190 - Container Security

  • Use private registries with TLS
  • Scan all images for vulnerabilities before deployment
  • Sign images and verify signatures
  • Implement RBAC on registry access
  • Enable audit logging

CIS Docker Benchmark

  • 2.5: Ensure insecure registries are not used
  • 4.2: Ensure containers use trusted base images
  • 4.4: Ensure images are scanned for vulnerabilities
  • 4.5: Ensure Content trust for Docker is enabled

Harbor Security Features

Feature Purpose
Trivy Scanner Vulnerability detection in images
Content Trust Image signing with Notary/Cosign
RBAC Role-based project access control
Vulnerability Prevention Block deployment of vulnerable images
Immutable Tags Prevent tag overwriting
Audit Logs Track all registry operations
Replication Secure cross-registry replication
Retention Policies Automated cleanup of old images
Robot Accounts Service-to-service authentication
OIDC/LDAP Enterprise identity integration
workflows.md1.0 KB

Workflows - Harbor Registry Security

Workflow 1: Secure Image Pipeline

[Build Image] --> [Push to Harbor] --> [Auto-Scan (Trivy)] --> [Sign (Cosign)]
                                              |
                                    +---------+---------+
                                    |                   |
                                    v                   v
                            Vulnerabilities?     No vulnerabilities
                            Block deployment     Allow pull

Workflow 2: Registry Hardening

Step 1: Enable HTTPS with valid TLS certificates
Step 2: Configure OIDC/LDAP authentication
Step 3: Create projects with auto-scan enabled
Step 4: Enable vulnerability prevention policy
Step 5: Configure content trust (Cosign)
Step 6: Set immutable tag rules for release tags
Step 7: Configure retention policies
Step 8: Enable audit logging
Step 9: Create robot accounts for CI/CD
Step 10: Test with vulnerability gate check

Scripts 2

agent.py6.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for securing container registry with Harbor.

Audits Harbor registry security configuration including RBAC,
vulnerability scanning policies, content trust, immutable tags,
and OIDC authentication via Harbor REST API v2.0.
"""

import json
import sys
from pathlib import Path
from datetime import datetime

try:
    import requests
except ImportError:
    requests = None


class HarborSecurityAgent:
    """Audits and hardens Harbor container registry security."""

    def __init__(self, harbor_url, username="admin", password="",
                 output_dir="./harbor_audit"):
        self.base_url = harbor_url.rstrip("/") + "/api/v2.0"
        self.auth = (username, password)
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []

    def _get(self, path, params=None):
        if not requests:
            return {"error": "requests library required"}
        resp = requests.get(f"{self.base_url}{path}", auth=self.auth,
                            params=params, verify=True, timeout=15)
        try:
            return resp.json()
        except (json.JSONDecodeError, ValueError):
            return {"status": resp.status_code}

    def audit_projects(self):
        """Audit all projects for security configuration."""
        projects = self._get("/projects", {"page_size": 100})
        if isinstance(projects, dict) and "error" in projects:
            return projects

        results = []
        for p in projects:
            meta = p.get("metadata", {})
            name = p.get("name", "")
            issues = []

            if meta.get("public") == "true":
                issues.append("Project is public - images accessible without auth")
            if meta.get("auto_scan") != "true":
                issues.append("Auto-scan on push not enabled")
            if meta.get("prevent_vul") != "true":
                issues.append("Vulnerability prevention not enabled")
            if meta.get("enable_content_trust") != "true":
                issues.append("Content trust (Notary) not enabled")
            if meta.get("enable_content_trust_cosign") != "true":
                issues.append("Cosign content trust not enabled")

            for issue in issues:
                self.findings.append({
                    "severity": "high" if "public" in issue or "prevent_vul" in issue else "medium",
                    "project": name,
                    "issue": issue,
                })

            results.append({
                "name": name,
                "public": meta.get("public"),
                "auto_scan": meta.get("auto_scan"),
                "prevent_vul": meta.get("prevent_vul"),
                "content_trust": meta.get("enable_content_trust"),
                "cosign": meta.get("enable_content_trust_cosign"),
                "issues": issues,
            })
        return results

    def audit_system_config(self):
        """Check system-level security configuration."""
        config = self._get("/configurations")
        if isinstance(config, dict) and "error" in config:
            return config

        checks = []
        auth_mode = config.get("auth_mode", {}).get("value", "db_auth")
        if auth_mode == "db_auth":
            self.findings.append({
                "severity": "medium",
                "issue": "Using local database auth instead of OIDC/LDAP",
            })
            checks.append({"check": "auth_mode", "value": auth_mode, "status": "WARN"})
        else:
            checks.append({"check": "auth_mode", "value": auth_mode, "status": "OK"})

        self_reg = config.get("self_registration", {}).get("value", True)
        if self_reg:
            self.findings.append({
                "severity": "high",
                "issue": "Self-registration enabled - anyone can create accounts",
            })
        checks.append({"check": "self_registration", "value": self_reg,
                        "status": "FAIL" if self_reg else "OK"})

        return checks

    def list_project_members(self, project_name):
        """List members and their roles for a project."""
        members = self._get(f"/projects/{project_name}/members")
        if isinstance(members, dict) and "error" in members:
            return members
        role_map = {1: "ProjectAdmin", 2: "Maintainer", 3: "Developer",
                    4: "Guest", 5: "LimitedGuest"}
        return [
            {"username": m.get("entity_name", ""), "role": role_map.get(m.get("role_id"), "Unknown")}
            for m in (members if isinstance(members, list) else [])
        ]

    def check_immutable_tags(self, project_name):
        """Check immutable tag rules for a project."""
        rules = self._get(f"/projects/{project_name}/immutabletagrules")
        if not rules or (isinstance(rules, dict) and "error" in rules):
            self.findings.append({
                "severity": "medium",
                "project": project_name,
                "issue": "No immutable tag rules configured",
            })
            return []
        return rules

    def audit_logs(self, page_size=20):
        """Retrieve recent audit log entries."""
        logs = self._get("/audit-logs", {"page_size": page_size})
        if isinstance(logs, dict) and "error" in logs:
            return logs
        return [
            {"operation": l.get("operation"), "resource": l.get("resource"),
             "username": l.get("username"), "time": l.get("op_time")}
            for l in (logs if isinstance(logs, list) else [])
        ]

    def generate_report(self):
        projects = self.audit_projects()
        sys_config = self.audit_system_config()

        report = {
            "report_date": datetime.utcnow().isoformat(),
            "harbor_url": self.base_url.replace("/api/v2.0", ""),
            "projects_audited": len(projects) if isinstance(projects, list) else 0,
            "system_config": sys_config,
            "projects": projects,
            "findings": self.findings,
            "finding_count": len(self.findings),
        }
        out = self.output_dir / "harbor_audit_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    if len(sys.argv) < 2:
        print("Usage: agent.py <harbor_url> [--user admin] [--pass Harbor12345]")
        sys.exit(1)

    url = sys.argv[1]
    user = "admin"
    password = "Harbor12345"
    if "--user" in sys.argv:
        user = sys.argv[sys.argv.index("--user") + 1]
    if "--pass" in sys.argv:
        password = sys.argv[sys.argv.index("--pass") + 1]

    agent = HarborSecurityAgent(url, user, password)
    agent.generate_report()


if __name__ == "__main__":
    main()
process.py6.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Harbor Container Registry Security Auditor

Audits Harbor registry configuration for security best practices
including scanning policies, content trust, RBAC, and TLS.
"""

import json
import sys
import urllib.request
import urllib.error
import ssl
import base64
from dataclasses import dataclass, field


@dataclass
class HarborFinding:
    category: str
    title: str
    severity: str
    details: str
    remediation: str


@dataclass
class HarborAuditReport:
    findings: list = field(default_factory=list)
    harbor_url: str = ""
    projects_audited: int = 0


def harbor_api_call(base_url: str, endpoint: str, username: str, password: str) -> dict:
    """Call Harbor API and return JSON response."""
    url = f"{base_url}/api/v2.0{endpoint}"
    credentials = base64.b64encode(f"{username}:{password}".encode()).decode()

    req = urllib.request.Request(url)
    req.add_header("Authorization", f"Basic {credentials}")
    req.add_header("Content-Type", "application/json")

    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE

    try:
        with urllib.request.urlopen(req, context=ctx, timeout=10) as response:
            return json.loads(response.read().decode())
    except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError) as e:
        print(f"[!] API call failed for {endpoint}: {e}")
        return {}


def audit_projects(base_url: str, username: str, password: str, report: HarborAuditReport):
    """Audit all projects for security configurations."""
    projects = harbor_api_call(base_url, "/projects?page=1&page_size=100", username, password)
    if not projects:
        return

    for project in projects:
        name = project.get("name", "unknown")
        metadata = project.get("metadata", {})
        report.projects_audited += 1

        # Check auto-scan
        if metadata.get("auto_scan") != "true":
            report.findings.append(HarborFinding(
                category="Scanning",
                title=f"Auto-scan disabled for project: {name}",
                severity="HIGH",
                details="Images pushed to this project are not automatically scanned",
                remediation=f"Enable auto_scan for project '{name}'"
            ))

        # Check vulnerability prevention
        if metadata.get("prevent_vul") != "true":
            report.findings.append(HarborFinding(
                category="Scanning",
                title=f"Vulnerability prevention disabled for project: {name}",
                severity="HIGH",
                details="Vulnerable images can be pulled from this project",
                remediation=f"Enable prevent_vul for project '{name}'"
            ))

        # Check content trust
        if metadata.get("enable_content_trust") != "true" and metadata.get("enable_content_trust_cosign") != "true":
            report.findings.append(HarborFinding(
                category="Content Trust",
                title=f"Content trust not enforced for project: {name}",
                severity="MEDIUM",
                details="Unsigned images can be used from this project",
                remediation=f"Enable content trust (Cosign) for project '{name}'"
            ))

        # Check public visibility
        if metadata.get("public") == "true":
            report.findings.append(HarborFinding(
                category="Access Control",
                title=f"Project is publicly accessible: {name}",
                severity="MEDIUM",
                details="Anyone can pull images from this project",
                remediation=f"Set project '{name}' to private unless intentionally public"
            ))


def audit_system_config(base_url: str, username: str, password: str, report: HarborAuditReport):
    """Audit system-level Harbor configuration."""
    config = harbor_api_call(base_url, "/configurations", username, password)
    if not config:
        return

    # Check auth mode
    auth_mode = config.get("auth_mode", {}).get("value", "db_auth")
    if auth_mode == "db_auth":
        report.findings.append(HarborFinding(
            category="Authentication",
            title="Using local database authentication",
            severity="MEDIUM",
            details="Harbor uses local DB auth instead of enterprise IdP",
            remediation="Configure OIDC or LDAP authentication"
        ))

    # Check self-registration
    self_reg = config.get("self_registration", {}).get("value", False)
    if self_reg:
        report.findings.append(HarborFinding(
            category="Authentication",
            title="Self-registration is enabled",
            severity="HIGH",
            details="Anyone can create an account on this Harbor instance",
            remediation="Disable self-registration in Harbor configuration"
        ))


def print_report(report: HarborAuditReport):
    print("\n" + "=" * 70)
    print("HARBOR REGISTRY SECURITY AUDIT")
    print("=" * 70)
    print(f"Harbor URL:         {report.harbor_url}")
    print(f"Projects Audited:   {report.projects_audited}")
    print(f"Findings:           {len(report.findings)}")
    print("=" * 70)

    for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
        findings = [f for f in report.findings if f.severity == sev]
        if findings:
            print(f"\n{sev} ({len(findings)}):")
            for f in findings:
                print(f"  [{f.category}] {f.title}")
                print(f"    Fix: {f.remediation}")


def main():
    import argparse
    parser = argparse.ArgumentParser(description="Harbor Registry Security Auditor")
    parser.add_argument("--url", required=True, help="Harbor URL (e.g., https://harbor.example.com)")
    parser.add_argument("--username", default="admin", help="Harbor admin username")
    parser.add_argument("--password", required=True, help="Harbor admin password")
    args = parser.parse_args()

    report = HarborAuditReport(harbor_url=args.url)

    print(f"[*] Auditing Harbor at {args.url}")
    audit_projects(args.url, args.username, args.password, report)
    audit_system_config(args.url, args.username, args.password, report)
    print_report(report)

    with open("harbor_audit_report.json", "w") as f:
        json.dump({
            "harbor_url": report.harbor_url,
            "findings": [{"category": f.category, "title": f.title,
                         "severity": f.severity, "remediation": f.remediation}
                        for f in report.findings]
        }, f, indent=2)
    print("\n[*] Report saved to harbor_audit_report.json")


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 0.5 KB
Keep exploring