hcsshim

repository·main·Indexed 20 days ago

https://github.com/microsoft/hcsshim

A Golang interface for the Windows Host Compute Service (HCS) used to launch and manage Windows Containers. It includes support for the Host Network Service (HNS), a guest agent for Linux Hyper-V containers (LCOW), and various internal tools such as a Policy Engine Simulator, the hvsocketaddr tool for resolving container IDs to VMIDs, and a securitypolicy tool for generating policies from TOML configurations.

Tokens
36.3K
Snippets
116
Records
171
Agent score
71%

What's inside hcsshim

  1. Use the securitypolicy tool to generate security policies

    main

    The securitypolicy tool (invoked via securitypolicytool) converts a TOML configuration file into various security policy formats, such as Base64 encoded JSON or Rego policies.

    Note: This tool is primarily intended for developers working on security policy functionality within this repository. It is not intended for general end-users, though it can serve as a basis for such a tool.

    Performance Warning: Running the tool can take a significant amount of time because it must download each layer for every container, convert them into an ext4 filesystem, and calculate the dm-verity root hash.

    securitypolicytool -c <config.toml> -t <format> -r
  2. Understand the Linux Hyper-V Container Guest Agent (LCOW) architecture

    main

    The code in the internal/guest directory is used to build the guest-side agent for Linux Hyper-V containers on Windows (LCOW). This agent is designed to run inside a custom Linux OS to support Linux container payloads. It acts as a process that the host machine connects to in order to execute requests for running containers within the Linux guest.

    Key architectural roles:

    • Host-Guest Communication: The agent facilitates communication between the Windows host and the Linux guest.
    • Separation of Concerns: This package serves as a boundary between Windows-specific logic (found elsewhere in the repository) and LCOW-specific guest features.
  3. How security policies and enforcement points work

    main

    The pkg/securitypolicy package allows users to express attested security policies using Rego.

    A security policy consists of multiple enforcement points. Each enforcement point constrains a specific action that the host requests of the guest.

    Users can write policies from scratch or use the provided framework.rego to simplify the process. Regardless of the approach, any valid policy must define enforcement points within the api.rego namespace.

  4. Handle pause containers in LCOW policies

    main

    All LCOW (Linux Containers on Windows) pods require a pause container. The securitypolicytool automatically adds a default version of the pause container to the policy, even if it is not explicitly defined in your TOML configuration.

    Note for developers: If the pause container version changes from 3.1, you must update the hardcoded root hash in this tool. You can compute the new root hash using the dmverity-vhd tool.

  5. Writing Rego policies for the simulator

    main

    Policies must use the package policy declaration. The simulator supports two main styles of policy implementation:

    Framework-based policy

    These policies rely on a predefined framework structure (usually accessed via data.framework). They define rules by mapping enforcement points to framework logic.

    Example:

    package policy
    
    import future.keywords.every
    import future.keywords.in
    
    // ... container definitions ...
    
    create_container := data.framework.create_container
    reason := {"errors": data.framework.errors}

    Custom Policy

    These policies define their own logic and data structures entirely. You can define custom rules for enforcement points like mount_device or create_container.

    Example:

    package policy
    
    // Custom logic for load_fragment
    default load_fragment := {"allowed": false}
    load_fragment := {"allowed": true, "add_module": true} {
        input.issuer == "did:web:contoso.github.io"
        input.feed == "contoso.azurecr.io/custom"
    }
    
    // Simple allow rules
    exec_in_container := {"allowed": true}
    package policy
    
    api_version := "0.7.0"
    
    // Example of a custom rule
    default load_fragment := {"allowed": false}
    load_fragment := {"allowed": true, "add_module": true} {
        input.issuer == "did:web:contoso.github.io"
        input.feed == "contoso.azurecr.io/custom"
    }
    
    // Example of a simple allow rule
    exec_in_container := {"allowed": true}
  6. How Containerd Shim V2 works

    main

    The V2 shims are a rewrite of the Windows containerd shim. While the V1 shim was a monolith handling LCOW, Hyper-V WCOW, and process-isolated containers, the V2 model splits these into focused, per-platform shims.

    Each V2 shim is backed 1:1 by a sandbox and implements two distinct APIs provided by containerd:

    1. Sandbox API: Used to manage the sandbox lifecycle.
    2. Task API: Used to manage containers and processes running inside the sandbox.

    All V2 shims honor the Kubernetes CRI pod model using specific annotations:

    • Sandbox/Pause Container: Annotated with "io.kubernetes.cri.container-type": "sandbox".
    • Workload Containers: Annotated with "io.kubernetes.cri.container-type": "container" and reference the sandbox via "io.kubernetes.cri.sandbox-id".
  7. Understand the components of a UVM

    main

    A User Virtual Machine (UVM) is composed of several distinct parts that work together during the boot process:

    • Linux kernel: The core operating system component.
    • Kernel command line: A set of parameters passed to the kernel that dictate its behavior during boot.
    • Root filesystem (rootfs) disk: The disk containing the initial set of files for the VM.
    • Startup script: A script stored within the rootfs disk that performs final system initialization.
    • Hash disk (SNP Mode only): A disk containing DM-Verity hash data used to ensure integrity in SNP Mode.
  8. Understand the scsi package architecture

    main

    The scsi package separates the concerns of SCSI device attachment and guest-side mounting into distinct layers to ensure they are tracked and handled independently.

    Layered Architecture

    1. Top Level: Manager

    An exported type that acts as the primary consumer interface. It wraps two unexported managers (attachManager and mountManager) to provide a unified API for attaching and mounting devices.

    2. Mid Level: State Management

    • attachManager: Manages the lifecycle of SCSI attachments. It tracks which devices are attached, manages controller/LUN slot allocation, and maintains a reference count for each attachment to ensure devices are not detached until all requests are matched.
    • mountManager: Manages the lifecycle of guest-side mounts. It tracks current mounts and their options, maintains reference counts, and ensures devices are not unmounted until all matching mount requests are cleared.

    3. Low Level: Backend Interfaces

    The mid-level managers rely on implementations of the following interfaces to perform actual operations:

    • HostBackend: Consists of the attacher interface, which handles host-side attach/detach operations.
    • GuestBackend: Consists of the mounter (guest-side mount/unmount) and unplugger (guest-side safe removal before detachment) interfaces.

    When instantiating a Manager, the client must provide concrete implementations of HostBackend and GuestBackend.

  9. Use metadata commands in Rego policies

    main

    The Rego Policy Interpreter allows rules to return metadata commands alongside the allowed decision. These commands modify a persistent state stored in the data.metadata namespace, which can be referenced by subsequent rule evaluations.

    Each metadata command must follow a specific JSON structure to define how the state is modified.

    {
        "name": "<metadata key>",
        "action": "<add|update|remove>",
        "key": "<key>",
        "value": "<optional value>"
    }
  10. Adding a new enforcement point to the security policy

    main

    To extend the security policy with a new enforcement point, follow this integration checklist to ensure the new rule is correctly connected across the codebase and supported by the Rego framework:

    1. Interface Update: Add the enforcement point to the SecurityPolicyEnforcer interface in securitypolicyenforcer.go.
    2. Stub Implementation: Add stub implementations to all classes implementing that interface (e.g., securitypolicyenforcer.go, securitypolicyenforcer_rego.go, and mountmonitoringsecuritypolicyenforcer.go).
    3. Host-side Guarding: Wrap the actual action call in uvm.go so the action only executes if the security policy permits it.
    4. Rego API Definition: Add the enforcement point to api.rego and increment the minor version.
    5. Policy Rules: Add the new rule to policy.rego and open_door.rego.
    6. Framework Integration: Add the rule logic and useful, rule-gated error messages to framework.rego.
    7. Data Models:
      • Update securitypolicy_internal.go to include any necessary constraint objects.
      • Update securitypolicy_marshal.go to ensure these constraint objects are emitted during Rego marshalling.
    8. Input Mapping: In securitypolicyenforcer_rego.go, implement the stub to provide the required input for the framework logic.
    9. Testing: Add tests in regopolicy_test.go. You should include:
      • A test verifying the rule enforces the constraint correctly.
      • At least one test for every error condition, verifying that the specific error messages are present.
  11. Linting the hcsshim repository

    main

    The project uses golangci-lint. Linting is performed on both the root and the ./test directories, and must be run for both GOOS=windows and GOOS=linux to ensure full coverage.

    # Run locally
    golangci-lint run
    
    # To show all errors, use:
    # golangci-lint run --max-issues-per-linter=0 --max-same-issues=0
    
    # Run across the entire repo for both Windows and Linux
    foreach ( $goos in ('windows', 'linux') ) {
        foreach ( $repo in ('.', 'test') ) {
            pwsh -Command "cd $repo && go env -w GOOS=$goos && golangci-lint.exe run --verbose"
        }
    }
  12. Configure image authorization in securitypolicy TOML

    main

    If an image is hosted in a registry requiring authentication, add an [auth] object to the specific [[container]] definition in your TOML file. Authorization is configured on a per-image basis. For anonymous access, omit the [auth] object.

    [[container]]
    image_name = "rust:1.52.1"
    command = ["rustc", "--help"]
    
    [auth]
    username = "my username"
    password = "my password"