OpNix

repository·main·Indexed 41 days ago

https://github.com/brizzbuzz/opnix

Secure, declarative integration for 1Password secrets within the Nix ecosystem, supporting NixOS, nix-darwin, and Home Manager. OpNix automates secret retrieval, manages file permissions, and ensures systemd or launchd services are automatically restarted when secrets change. It includes a CLI for managing service account tokens and resolving environment variables via the `opnix env` command.

Tokens
34.4K
Snippets
97
Records
120
Agent score
52%

What's inside OpNix

  1. How OpNix manages secrets and services

    main

    OpNix acts as a bridge between the 1Password Service and your local system services.

    The Workflow

    1. Authentication: OpNix uses a secure token (stored in tokenFile) to authenticate with the 1Password Service via its API.
    2. Deployment: OpNix retrieves the secrets and deploys them to the specified path with the configured owner and mode.
    3. Service Integration: OpNix manages systemd (Linux) or launchd (macOS) services. When a secret is updated, OpNix detects the change and automatically restarts the services listed in the services array for that secret.

    Reliability Features (V1)

    • Graceful Degradation: If the token is missing, the system will still boot, preventing complete system lockouts.
    • Change Detection: Secrets are only updated and services restarted if the content actually changes.
    • Error Handling: Uses proper system services instead of activation scripts to ensure robust error recovery and rollback capabilities.
  2. Use path templates for organized secret storage

    main

    When managing many certificates, you can use pathTemplate and variables to avoid repetitive path definitions. OpNix will construct the file path using the template and the provided variables.

    Example pattern:

    1. Define a pathTemplate like /etc/ssl/{service}/{domain}/{name}.
    2. Set defaults for common segments like service = "caddy".
    3. In each secret, provide variables (e.g., domain = "example.com") to fill the template.
    services.onepassword-secrets = {
      enable = true;
      tokenFile = "/etc/opnix-token";
      
      pathTemplate = "/etc/ssl/{service}/{domain}/{name}";
      defaults = {
        service = "caddy";
      };
      
      secrets = {
        exampleComCert = {
          reference = "op://Homelab/SSL-Example-Com/certificate";
          variables = { domain = "example.com"; };
          # Resulting path: /etc/ssl/caddy/example.com/certificate
          # Note: 'name' is inferred from the secret name if not explicitly set
          owner = "caddy";
          group = "caddy";
          mode = "0644";
          services = ["caddy"];
        };
      };
    };
  3. Security best practices for PostgreSQL with OpNix

    main

    When using OpNix for PostgreSQL secret management, adhere to these security guidelines:

    1. Password Security: Use strong, unique passwords for all database users.
    2. File Permissions: Ensure secret files have restrictive permissions (0600).
    3. Network Security: Configure pg_hba.conf to restrict network access.
    4. SSL/TLS: Enable SSL for all remote connections.
    5. Backup Encryption: Encrypt database backups before storing them remotely.
    6. Audit Logging: Enable PostgreSQL audit logging for security monitoring.
    7. Regular Updates: Keep both PostgreSQL and OpNix updated to the latest versions.
  4. How OpNix manages secrets on different platforms

    main

    OpNix uses different system mechanisms to manage secrets depending on the platform:

    • NixOS: Uses systemd services for reliable management. Secrets are stored in /var/lib/opnix/secrets/ by default. It supports service integration and automatic restarts when secrets change.
    • macOS (nix-darwin): Uses launchd services. Secrets are stored in /usr/local/var/opnix/secrets/ by default. Note that macOS requires explicit groupId configuration and users must be added to the onepassword-secrets group.
    • Home Manager: Works on any platform. It runs during Home Manager activation. Relative secret paths resolve under the user's home directory, though absolute paths are also supported.
  5. Security best practices for OpNix secrets

    main

    When using OpNix to manage secrets, follow these security guidelines:

    • File Permissions: Always ensure private keys and tokens have 0600 permissions.
    • Token Storage: Choose between a system token (requires membership in the onepassword-secrets group) or a user token.
    • SSH Agent: Use an SSH agent to manage keys and avoid repeated passphrase entry.
    • Environment Variables: Exercise caution when injecting secrets into environment variables, as they may be visible to other processes.
    • Git Commits: Never commit actual secrets to version control.
    • Backup Strategy: Ensure your 1Password vault is properly backed up.
    • Token Rotation: Regularly rotate your API tokens and SSH keys.
  6. Security best practices for SSL certificates

    main

    When managing SSL certificates with OpNix and Caddy, follow these security guidelines:

    • Private Key Security: Always use mode 0600 for private keys.
    • File Ownership: Ensure the caddy user owns the certificate files.
    • Directory Permissions: Restrict /etc/ssl/private to mode 700.
    • Monitoring: Set up expiry monitoring and alerts for your certificates.
    • Backups: Maintain encrypted backups of your certificates.
    • Logging: Enable access logs for security monitoring.
  7. Manage secret rollback safety and 1Password references

    main

    Because NixOS generations retain OpNix configuration (including op:// references), but secret values are resolved dynamically by the opnix-secrets.service, deleting or renaming a field in 1Password can break older NixOS generations during a rollback.

    Best Practices for Changing Secrets

    To ensure safe rollbacks, use an overlap window when changing a secret reference:

    1. Create the new field in 1Password.
    2. Update the Nix configuration to point to the new op:// reference.
    3. Build and test the new generation before switching.
    4. Verify opnix-secrets.service and its journal logs after the switch.
    5. Retain the old field in 1Password as long as any previous NixOS generation might still be used as a rollback target.
    6. Remove the old field only after the rollback window has closed.

    Troubleshooting Rollbacks

    If an older generation fails with exit code 65, it likely means a required secret reference is missing. To fix this:

    1. Restore the retired field in 1Password.
    2. Restart opnix-secrets.service.
    3. Retain the field until the generation is no longer a valid rollback target.
  8. Advanced: Multi-database setup with OpNix path templates

    main

    For complex environments with multiple databases, use pathTemplate in services.onepassword-secrets to organize secrets in a structured directory (e.g., /run/secrets/postgres/{database}/{name}).

    You This allows you to:

    1. Use lib.mapAttrs' to dynamically generate secrets for a list of database configurations.
    2. Use variables within a secret definition to populate the pathTemplate.
    3. Dynamically generate the initialScript and authentication rules for PostgreSQL using Nix functions like lib.concatMapStringsSep.
    4. Automatically create the necessary directory structure using systemd.tmpfiles.rules.
    { config, pkgs, lib, ... }: {
      services.onepassword-secrets = {
        enable = true;
        tokenFile = "/etc/opnix-token";
        pathTemplate = "/run/secrets/postgres/{database}/{name}";
        
        secrets = {
          "postgres-superuser-password" = {
            reference = "op://Homelab/PostgreSQL-Main/password";
            path = "/run/secrets/postgres/superuser-password";
            owner = "postgres";
            group = "postgres";
            mode = "0600";
            services = ["postgresql"];
          };
        } // (lib.mapAttrs' (dbName: dbConfig: 
            lib.nameValuePair $"db-${dbName}-password" {
              reference = dbConfig.passwordRef;
              variables = { database = dbName; name = "password"; };
              owner = "postgres";
              group = "postgres";
              mode = "0600";
              services = ["postgresql"];
            }
          ) databases);
      };
      # ... PostgreSQL service configuration
    }
  9. OpNix Validation and Security Best Practices

    main

    Configuration Validation

    OpNix performs automatic validation on the following:

    • File permissions: Must be valid octal (e.g., 0644, 0600).
    • 1Password references: Must follow op://Vault/Item/field or op://Vault/Item/Section/field format.
    • Path conflicts: Prevents multiple secrets from sharing the same output path.
    • User/group existence: Validates that specified users and groups exist.
    • Configuration completeness: Ensures at least one of configFiles or secrets is specified.

    Security Recommendations

    Token File Security

    • Store tokens with restricted permissions (640 or 600).
    • Never commit tokens to version control.
    • Use separate tokens for different environments.
    • Rotate tokens regularly.

    Secret File Permissions

    • Use restrictive permissions by default (0600).
    • Only grant broader access when necessary (0644, 0640).
    • Ensure parent directories have appropriate permissions.

    Service Account Permissions

    • Grant minimal required vault access.
    • Use separate service accounts for different environments.
    • Monitor service account activity and regularly audit vault access permissions.
  10. Declare secrets in Nix using services.onepassword-secrets.secrets

    main

    In NixOS, you can declare secrets by mapping 1Password references to specific file attributes. You can define a basic secret with a reference, owner, group, and mode, or extend it with service integration to manage service restarts and dependencies when the secret changes.

    # Basic Secret Declaration
    services.onepassword-secrets.secrets = {
      serviceCredential = {
        reference = "op://Vault/Item/field";
        owner = "service-user";
        group = "service-group";
        mode = "0600";
        services = ["service-name"];
      };
      
      sslCertificate = {
        reference = "op://Homelab/SSL Certificates/example.com/cert";
        owner = "caddy";
        group = "caddy";
        mode = "0644";
      };
    };
    
    # Custom Path with Service Integration
    services.onepassword-secrets.secrets = {
      sslCertificate = {
        reference = "op://Homelab/SSL/certificate";
        path = "/etc/ssl/certs/app.pem";
        owner = "caddy";
        group = "caddy";
        mode = "0644";
        services = {
          caddy = {
            restart = true;
            after = ["opnix-secrets.service"];
          };
        };
      };
    };
  11. Use path templates for secret paths

    main

    OpNix allows generating secret paths dynamically using a pathTemplate and variables.

    Configuration

    • pathTemplate: A string template for generating paths. Supported variables include {service}, {environment}, {name}, and any custom variables defined in secrets.<name>.variables.
    • defaults: A set of default values for template variables used across all secrets.

    Example: If pathTemplate = "/etc/secrets/{service}/{environment}/{name}" and defaults.environment = "prod", a secret named apiKey for service app would be placed at /etc/secrets/app/prod/apiKey.

    services.onepassword-secrets = {
      pathTemplate = "/etc/secrets/{service}/{environment}/{name}";
      defaults = {
        environment = "prod";
        service = "app";
      };
    };
  12. Format 1Password References

    main

    All 1Password references must follow the pattern: op://VaultName/ItemName/FieldName

    Examples:

    • op://Homelab/Database/password
    • op://Personal/SSH-Keys/private-key
    • op://Work/API-Tokens/github-token

    Special Fields:

    • password: The item's password field.
    • username: The item's username field.
    • notes: The item's notes field.

    Binary Documents and Attachments: To retrieve a file (Document or attachment), set kind = "file". The reference must have exactly three components: op://Vault/Item/filename

    Note: The filename must match the Document or attachment name exactly. OpNix does not automatically fall back from field resolution to file resolution. The 1Password SDK limits file operations to 50 MB.