Full Code of 1Password/connect for AI

main 7485a5981357 cached
24 files
121.6 KB
29.7k tokens
1 requests
Download .txt
Repository: 1Password/connect
Branch: main
Commit: 7485a5981357
Files: 24
Total size: 121.6 KB

Directory structure:
gitextract__1cd96aw/

├── CHANGELOG.md
├── README.md
├── docs/
│   ├── configuration.md
│   └── openapi/
│       └── spec.yaml
└── examples/
    ├── aws-ecs-fargate/
    │   ├── README.md
    │   └── connect-server.yaml
    ├── beta/
    │   ├── aws-ecs-apigateway-cfn/
    │   │   ├── README.md
    │   │   └── connect-server.yaml
    │   ├── azure-container-apps-arm/
    │   │   ├── README.md
    │   │   └── aca-op-connect-server-template.json
    │   └── docker/
    │       ├── README.md
    │       ├── compose.custom-tls.yaml
    │       ├── compose.http.yaml
    │       ├── compose.lets-encrypt.yaml
    │       ├── compose.template.yaml
    │       ├── connect-api.env
    │       └── connect-sync.env
    ├── docker/
    │   └── compose/
    │       ├── README.md
    │       └── docker-compose.yaml
    └── kubernetes/
        ├── README.md
        ├── op-connect-deployment.yaml
        ├── op-connect-ingress.yaml
        ├── op-connect-issuer.yaml
        └── op-connect-service.yaml

================================================
FILE CONTENTS
================================================

================================================
FILE: CHANGELOG.md
================================================
[//]: # "START/v1.8.0"

# v1.8.0
This release includes improvements for fetching files with Connect, like support for fetching a file referenced in multiple items and a better error response if a file is not synced yet.

- [IMPROVED] Connect now returns an improved error response when you try to fetch a file that isn't synced in your Connect server's local storage. {4378}

- [FIXED] Connect now allows you to fetch a file if the file is referenced in multiple items across different vaults. {4262}
- [FIXED] Connect file storage management can now handle multiple items referencing the same file. {4263}

[//]: # "START/v1.7.3"

# v1.7.3
This release updates the dependencies and the images used to build Connect.

- [IMPROVED] Update go version to 1.22.4. {4146}

- [SECURITY] Upgraded to Go 1.21.x and Debian 12 Bookworm for building and CI. {3870}
- [SECURITY] Bump google.golang.org/grpc to address a vulnerable dependency alert. {3855}

[//]: # "START/v1.7.2"

# v1.7.2
This release contains bug fixes.

- [FIXED] Connect can update Document items. {3709}
- [FIXED] The first request sent to a new Connect server should not respond with a '401 invalid bearer token' error. {3711}

[//]: # "START/v1.7.1"

# v1.7.1
This release contains security and performance improvements.
- [IMPROVED] Connect item usage flushing has been optimised to decrease network traffic to the 1Password servers. {2600}

- [SECURITY] Addressed bug where PATCH could be abused to read items using a write only token. Credit to Carlos Baraza. {3392}


[//]: # "START/v1.7.0"

# v1.7.0
This release improves an error message and fixes two bugs.
- [IMPROVED] Creating a new DOCUMENT item now correctly returns an error message that this is not supported. {3127}

- [FIXED] Connect should no longer sporadically return an "database is locked" error when synchronising with the 1Password servers. {3381}
- [FIXED] Requests to /v1/vaults/{vaultUUID}/items no longer causes "Invalid Item UUID" to be logged. {3254}


[//]: # "START/v1.6.1"

# v1.6.1
This release addresses memory leak issues for Connect.
- [FIXED] Addressed a problem that caused Connect's memory consumption to increase with about 100B for every request handled. {2842}
- [FIXED] Addressed a problem that could cause Connect's memory consumption to increase by more than 1KB per request served after one of the containers (connect-api or connect-sync) disconnected. {3362}

[//]: # "START/v1.6.0"

# v1.6.0
This release moves Connect to a distroless base image.
- [SECURITY] Connect Docker images  are now based on gcr.io/distroless/base to reduce the number of false positives when scanned with container security tools. {3296}

[//]: # "START/v1.5.8"

# v1.5.8
This release contains two security improvements.
- [SECURITY] Addressed an issue that allows an attacker with network access to the Connect server to allocate high amounts of memory on the Connect server if it is configured to serve connections over TLS. We inherited this issue from the Go standard library, where the issue has been assigned vulnerability identifier CVE-2022-41717. {3274}
- [SECURITY] Update Debian packages for the Docker release images. {3195}

[//]: # "START/v1.5.7"

# v1.5.7
Security updates, and a fix to allow Connect to (re)start if the 1Password server is unavailable.
- [FIXED] The API container will now continue to serve requests if it is restarted when 1Password server in unreachable. {2560}

- [SECURITY] Update connect images to use latest base image. {2738}
- [SECURITY] Update Debian packages for the Docker release images. {2694}

[//]: # "START/v1.5.6"

# v1.5.6
This version addresses bugs related to file objects

- [FIXED] The file objects now have the correct ID and name. {2625, 2616}
- [FIXED] Categories route will no longer return status 500 when no error is present. {2170}

[//]: # "START/v1.5.5"

# v1.5.5
This version addresses a few bugs and a security improvement.

- [FIXED] Connect now properly returns the `content_path` attribute of the File object. {2598}

- [SECURITY] Updated Debian packages for the Docker release images. {2516}

[//]: # "START/v1.5.4"

# v1.5.4
This release updates the API spec of Connect to mention some supported fields and addresses two bugs.

- [IMPROVED] The API spec now mentions the "label" field for autofill URL's. {2446}
- [IMPROVED] The API spec now mentions support for the item categories "MEDICAL_RECORD" and "SSH_KEY". {2468}

- [FIXED] Connect accepts JSON inputs with camelCase keys as used in the SDK's again. {2317}
- [FIXED] Connect no longer return an error mentioning the files directory in some cases when a file is uploaded to a vault that Connect syncs. {2498,2539}

- [SECURITY] The Docker images are now using an updated version of the Debian base images. {2516}

[//]: # "START/v1.5.3"

# v1.5.3
This release contains improvements in validation during the SRP process.

- [SECURITY] Improved validation of server parameters in SRP process. Credits to Cure53. {2442}

[//]: # "START/v1.5.2"

# v1.5.2
This release addressed a few minor bugs and a memory leak.

- [IMPROVED] Connect now validates `monthYear` and `date` field types. {2075}

- [FIXED] Addressed a problem that could let the memory consumption of the sync container slowly increase for every incoming request under certain circumstances. {2017}
- [FIXED] Having a single quote as a filter value no longer throws a stack trace and now returns a 400. {1442}

[//]: # "START/v1.5.1"

# v1.5.1
This release addresses a few problems related to Connect's synchronization mechanism.

- [IMPROVED] Connect stays better in sync with 1Password when it experiences connection problems. {2150}

- [FIXED] Fixed synchronization of templates that could prevent Connect from starting after changing its credentials. {2151}
- [FIXED] Templates should now correctly synchronize to Connect after emptying Connect's database. {2151}
- [FIXED] Connect should no longer return a "failed to initialize database" error when upgrading from Connect v1.3.x. {2005}

[//]: # "START/v1.5.0"

# v1.5.0
This release introduces Docker images for the `arm64` and `arm/v7` platforms.

Furthermore, the bus between the api and sync container can now be manually configured for environments that cannot run containers with the `NET_BROADCAST` capability.

- [NEW] Bus auto-discovery can now be disabled by explicitly defining peers with the `$OP_BUS_PEERS` environment variable. {1778}
- [NEW] Connect API and Connect Sync Docker images for arm64/v8 and arm/v7 architectures are now available. {1771}

- [FIXED] The label field is now always correctly set (instead of being empty) for fields that have a USERNAME or PASSWORD purpose. {1657}



[//]: # "START/v1.4.0"

# v1.4.0
This release introduces enhancements for OTP fields, generated passwords and item usage histories.

This update includes non-breaking changes to the  [OpenAPI spec](docs/openapi/spec.yaml).

- [NEW] Specific characters can now be excluded from generated passwords by setting `excludeCharacters` in the recipe. {1631}
- [IMPROVED] Added the generated TOTP code to the body of OTP fields in Items. {1557}
- [IMPROVED] Connect now records more detailed item usage histories. {14198}


[//]: # "START/v1.3.0"

# v1.3.0

This release introduces support for accessing documents and files attached to items.

## Features

- [NEW] Add support for getting files through the API. Details can be found in the [OpenAPI spec](docs/openapi/spec.yaml).

- [IMPROVED] Item modification requests are now rejected by the Connect API if the authenticated account is in the frozen billing state. {1175}
- [IMPROVED] Items containing one or more file fields can now be updated through Connect. {1386}
- [IMPROVED] API spec now defines valid vault and item IDs. {1399}
- [IMPROVED] Connect API now reports its version number in the `1Password-Connect-Version` header. {1443}


## Fixed

- [FIXED] Resolved an issue that prevented servers from authenticating with accounts after an admin updated the sign-in url. {1446}


[//]: # "START/v1.2.0"

# v1.2.0

This release contains several improvements to securing you API requests and better error handling when dealing with Items.

## Features

- [NEW] Add TLS for the Connect API. Provide your own certificates or use LetsEncrypt

- [IMPROVED] A more user-friendly error is returned when the user attempts to create an item with the "CUSTOM" category. {1382}

## Fixed

- [FIXED] Resolved issue where API could incorrectly report a successful item deletion. {1368}
- [FIXED] Fixed the handling of TOTP fields for items in Connect {1372}

[//]: # "START/v1.1.1"

# v1.1.1

This release contains improvements to account synchronization and token validation.

[//]: # "START/v1.1.0"

# v1.1.0

This release contains several improvements to configuring, running, and troubleshooting a Secrets Automation deployment in your environment.

The health endpoint returns additional information about the status of the local item database and the "DELETE" endpoint now permanently deletes items instead of moving them to the Trash.

## Features

- [NEW] Updated the Connect API to delete items instead of trashing them. {11613}
- [NEW] Connect can now also be unlocked by sending a bearer token to the /health endpoint. {1305}
- [NEW] The /health endpoint reports the status of the data in the local database under account_data. {1305}
- [NEW] Added support for new API Credential item category. {1344}

- [IMPROVED] The `OP_LOG_LEVEL` evironment variable can now be used to customize the logging level of the Connect server and the sync component. {1205}
- [IMPROVED] The API request timeout for waiting for a healthy sync is now per-request and can be changed with the `OP_SYNC_TIMEOUT` environment variable. {1305}
- [IMPROVED] Item usage is now reported to 1password.com in batches at most once a second to avoid being rate limited. {1331}
- [IMPROVED] Syncer applies an exponential backoff when retrying if it cannot find a valid credential. {1341}

## Fixes

- [FIXED] Corrected several minor mistakes in the OpenAPI spec. {1326}
- [FIXED] Log error when a connection problem causes the failure of a Connect request. {1302}
- [FIXED] Updating the password for a Login or Password Item correctly updates the Password History. {1350}

[//]: # "START/v1.0.0"

# v1.0.0

The 1Password Connect server provides applications and services with information from 1Password as part of 1Password Secrets Automation.


================================================
FILE: README.md
================================================
<img src="https://github.com/1Password/connect/assets/46452606/0f7cf2a8-a290-41fc-b78d-3dfb1017f9be" alt="" role="img" >


<div align="center">
  <h1>1Password Connect</h1>
  <p>Access your 1Password secrets using 1Password Connect</p>
  <a href="https://developer.1password.com/docs/connect/get-started">
    <img alt="Get started" src="https://user-images.githubusercontent.com/45081667/226940040-16d3684b-60f4-4d95-adb2-5757a8f1bc15.png" height="37"/>
  </a>
</div>

---

With 1Password Connect, you can securely access your 1Password items and vaults in your company's apps and cloud infrastructure using a private REST API or the 1Password CLI.
## ✨ Get started
Check out our [developer documentation](https://developer.1password.com/docs/connect/get-started) to get started. Or see our deployment examples [here](https://github.com/1Password/connect/tree/main/examples).
## 💙 Community & Support

- File an [issue](https://github.com/1Password/connect/issues) for bugs and feature requests.
- Join the [Developer Slack workspace](https://developer.1password.com/joinslack)
- Subscribe to the [Developer Newsletter](https://1password.com/dev-subscribe/)



================================================
FILE: docs/configuration.md
================================================
# Connect Server Configuration

The Connect server consists of 2 containers, `1password/connect-api` and `1password/connect-sync` running in the same network. The two containers require a shared volume to store an encrpyted copy of your data.

## Data Volume

- `/home/opuser/.op/data`: The default location of the shared volume

**Note**: If you use are setting the `XDG_DATA_HOME` environment variable to a path other than `/home/opuser` you will need to mount your data volume at that path as well.

## Environment variables

The following environment variable configuration options are available for both contains:

- `OP_SESSION`: path to the 1password-credentials.json file
- `OP_HTTP_PORT`: port used by the HTTP server
- `OP_LOG_LEVEL`: set the logging level of the container
- `XDG_DATA_HOME`: set the path where the `.op/data` directory should be created
- `OP_BUS_PORT`: the port used for listening to incoming bus connections from other containers (by default, this is a random free port)
- `OP_BUS_PEERS`: a comma-separated listed of `[hostname]:[bus port]` pairs of other containers to connect to (see _Manual bus configuration_ for more details)

All other configuration options are only relevant for the `1password/connect-api` container:

- `OP_HTTPS_PORT`: port used by the HTTP sever when TLS is configured (see below)
- `OP_SYNC_TIMEOUT`: define how long to wait for initial sync to complete

When using TLS with own certificate:

- `OP_TLS_KEY_FILE`: path to the private key file.
- `OP_TLS_CERT_FILE`: path to the certificate file. This should be the full certificate chain.

When using TLS with Let's Encrypt:

- `OP_TLS_USE_LETSENCRYPT`: should be set to any value.
- `OP_TLS_DOMAIN`: the (sub-)domain for which to request a certificate. The DNS-records for this domain must point to the Connect server.

## Manual bus configuration (v1.5.0 and above)
By default, the 2 containers automatically discover and connect to the shared bus. This discovery mechanism requires the `NET_BROADCAST` capability, which cannot always be granted to containers.

It is also possible to manually configure the shared bus:
1. Assign a static port for the bus by setting the `OP_BUS_PORT` environment variable to a free port for all containers.
2. Set the `OP_BUS_PEERS` environment variable for the api container to `[hostname]:[bus port]`, where hostname is the name of the sync container and bus port the value of `OP_BUS_PORT` for the sync container.
3. Set the `OP_BUS_PEERS` environment variable for the sync container to `[hostname]:[bus port]`, where hostname is the name of the api container and bus port the value of `OP_BUS_PORT` for the api container.

For example, if the containers are called `op-connect-api` and `op-connect-sync`. The configuration of `op-connect-api` would be:
```
OP_BUS_PORT=11223
OP_BUS_PEERS=op-connect-sync:11223
```
And for `op-connect-sync`:
```
OP_BUS_PORT=11223
OP_BUS_PEERS=op-connect-api:11223
```


================================================
FILE: docs/openapi/spec.yaml
================================================
openapi: "3.0.2"
info:
  title: 1Password Connect
  description: >-
    REST API interface for 1Password Connect.
  version: "1.7.1"
  contact:
    name: 1Password Integrations
    email: support@1password.com
    url: https://support.1password.com/
servers:
  - url: http://localhost:8080/v1

tags:
  - name: Items
    description: Access and manage items inside 1Password Vaults
  - name: Vaults
    description: Access 1Password Vaults
  - name: Activity
    description: Access API Request Activity

components:
  securitySchemes:
    ConnectToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    ErrorResponse:
      type: object
      properties:
        status:
          type: integer
          description: HTTP Status Code
        message:
          type: string
          description: A message detailing the error
    File:
      type: object
      properties:
        id:
          type: string
          description: ID of the file
        name:
          type: string
          description: Name of the file
        size:
          type: integer
          description: Size in bytes of the file
        content_path:
          type: string
          description: Path of the Connect API that can be used to download the contents of this file.
          readOnly: true
        section:
          type: object
          description: For files that are in a section, this field describes the section.
          properties:
            id:
              type: string
        content:
          type: string
          format: byte
          description: Base64-encoded contents of the file. Only set if size <= OP_MAX_INLINE_FILE_SIZE_KB kb and `inline_files` is set to `true`.
      example:
        id: 6r65pjq33banznomn7q22sj44e
        name: foo.txt
        size: 35
        content_path: v1/vaults/ionaiwtdvgclrixbt6ztpqcxnq/items/p7eflcy7f5mk7vg6zrzf5rjjyu/files/6r65pjq33banznomn7q22sj44e/content
        content: VGhlIGZ1dHVyZSBiZWxvbmdzIHRvIHRoZSBjdXJpb3VzLgo=
    Vault:
      type: object
      properties:
        id:
          type: string
          pattern: '^[\da-z]{26}$'
        name:
          type: string
        description:
          type: string
        attributeVersion:
          type: integer
          description: The vault version
        contentVersion:
          type: integer
          description: The version of the vault contents
        items:
          description: Number of active items in the vault
          type: integer
        type:
          type: string
          enum:
            - USER_CREATED
            - PERSONAL
            - EVERYONE
            - TRANSFER
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
    GeneratorRecipe:
      description: The recipe is used in conjunction with the "generate" property to set the character set used to generate a new secure value
      type: object
      properties:
        length:
          type: integer
          description: Length of the generated value
          default: 32
          minimum: 1
          maximum: 64
        characterSets:
          type: array
          items:
            type: string
            enum:
              - LETTERS
              - DIGITS
              - SYMBOLS
          minimum: 0
          maximum: 3
          uniqueItems: true
        excludeCharacters:
          type: string
          description: "List of all characters that should be excluded from generated passwords."
          example: "abc1"
    Item:
      type: object
      required:
        - vault
        - category
      properties:
        id:
          type: string
          pattern: '^[\da-z]{26}$'
        title:
          type: string
        vault:
          type: object
          required:
            - id
          properties:
            id:
              type: string
              pattern: '^[\da-z]{26}$'
        category:
          type: string
          enum:
            - "LOGIN"
            - "PASSWORD"
            - "API_CREDENTIAL"
            - "SERVER"
            - "DATABASE"
            - "CREDIT_CARD"
            - "MEMBERSHIP"
            - "PASSPORT"
            - "SOFTWARE_LICENSE"
            - "OUTDOOR_LICENSE"
            - "SECURE_NOTE"
            - "WIRELESS_ROUTER"
            - "BANK_ACCOUNT"
            - "DRIVER_LICENSE"
            - "IDENTITY"
            - "REWARD_PROGRAM"
            - "DOCUMENT"
            - "EMAIL_ACCOUNT"
            - "SOCIAL_SECURITY_NUMBER"
            - "MEDICAL_RECORD"
            - "SSH_KEY"
            - "CUSTOM"
        urls:
          type: array
          items:
            type: object
            required:
              - href
            properties:
              label:
                type: string
              primary:
                type: boolean
              href:
                type: string
                format: url
          example:
            - primary: true
              href: https://example.com
            - href: https://example.org
        favorite:
          type: boolean
          default: false
        tags:
          type: array
          items:
            type: string
        version:
          type: integer
        state:
          type: string
          readOnly: true
          enum:
            - "ARCHIVED"
            - "DELETED"
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
        lastEditedBy:
          type: string
          readOnly: true
    FullItem:
      allOf:
        - $ref: "#/components/schemas/Item"
        - type: object
          properties:
            sections:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  label:
                    type: string
            fields:
              type: array
              items:
                $ref: '#/components/schemas/Field'
            files:
              type: array
              items:
                $ref: '#/components/schemas/File'
    Field:
      type: object
      required:
        - id
        - type
      properties:
        id:
          type: string
        section:
          type: object
          properties:
            id:
              type: string
        type:
          type: string
          default: "STRING"
          enum:
            - "STRING"
            - "EMAIL"
            - "CONCEALED"
            - "URL"
            - "TOTP"
            - "DATE"
            - "MONTH_YEAR"
            - "MENU"
        purpose:
          description: Some item types, Login and Password, have fields used for autofill. This property indicates that purpose and is required for some item types.
          type: string
          enum:
            - ""
            - "USERNAME"
            - "PASSWORD"
            - "NOTES"
        label:
          type: string
        value:
          type: string
        generate:
          description: If value is not present then a new value should be generated for this field
          type: boolean
          default: false
        recipe:
          $ref: "#/components/schemas/GeneratorRecipe"
        entropy:
          description: For fields with a purpose of `PASSWORD` this is the entropy of the value
          type: number
          readOnly: true
    APIRequest:
      description: Represents a request that was made to the API. Including what Token was used and what resource was accessed.
      type: object
      properties:
        requestId:
          description: The unique id used to identify a single request.
          type: string
          format: uuid
        timestamp:
          description: The time at which the request was processed by the server.
          type: string
          format: date-time
          readOnly: true
        action:
          type: string
          enum:
            - READ
            - CREATE
            - UPDATE
            - DELETE
        result:
          type: string
          enum:
            - SUCCESS
            - DENY
        actor:
          type: object
          properties:
            id:
              type: string
              format: uuid
            account:
              type: string
            jti:
              type: string
            userAgent:
              type: string
            requestIp:
              type: string
        resource:
          type: object
          properties:
            type:
              type: string
              enum:
                - ITEM
                - VAULT
            vault:
              type: object
              properties:
                id:
                  type: string
                  pattern: '^[\da-z]{26}$'
            item:
              type: object
              properties:
                id:
                  type: string
                  pattern: '^[\da-z]{26}$'
            itemVersion:
              type: integer
    Patch:
      type: array
      items:
        type: object
        properties:
          op:
            type: string
            enum: [ add, remove, replace ]
          path:
            type: string
            description: An RFC6901 JSON Pointer pointing to the Item document, an Item Attribute, and Item Field by Field ID, or an Item Field Attribute
            example: "/fields/06gnn2b95example10q91512p5/label"
          value:
            type: object
        required:
          - op
          - path
    ServiceDependency:
      description: The state of a registered server dependency.
      type: object
      properties:
        service:
          type: string
        status:
          type: string
        message:
          type: string
          description: Human-readable message for explaining the current state.


paths:
  /activity:
    get:
      operationId: GetApiActivity
      tags:
        - Activity
      summary: Retrieve a list of API Requests that have been made.
      security:
        - ConnectToken: [ ]
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            example: 10
            default: 50
          description: How many API Events should be retrieved in a single request.
        - in: query
          name: offset
          schema:
            type: integer
            example: 50
            default: 0
          description: How far into the collection of API Events should the response start
      responses:
        "200":
          description: OK
          headers:
            Content-Range:
              description: An decription of what part of the collection has been returned as well as the total size.
              schema:
                type: string
                example: 1-50/1134
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/APIRequest"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
  /vaults:
    get:
      operationId: GetVaults
      tags:
        - Vaults
      summary: Get all Vaults
      security:
        - ConnectToken: [ ]
      parameters:
        - in: query
          name: filter
          schema:
            type: string
            example: name eq "Some Vault Name"
          description: Filter the Vault collection based on Vault name using SCIM eq filter
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Vault"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
  /vaults/{vaultUuid}:
    get:
      operationId: GetVaultById
      tags:
        - Vaults
      summary: Get Vault details and metadata
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Vault to fetch Items from
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Vault"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: Vault not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 404
                message: vault {itemUuid} not found
  /vaults/{vaultUuid}/items:
    get:
      operationId: GetVaultItems
      tags:
        - Items
      summary: Get all items for inside a Vault
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Vault to fetch Items from
        - in: query
          name: filter
          schema:
            type: string
            example: title eq "Some Item Name"
          description: Filter the Item collection based on Item name using SCIM eq filter
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Item"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "404":
          description: Vault not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 404
                message: vault {vaultUuid} not found
    post:
      operationId: CreateVaultItem
      tags:
        - Items
      summary: Create a new Item
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Vault to create an Item in
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FullItem"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FullItem"
        "400":
          description: Unable to create item due to invalid input
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 400
                message: Invalid item category
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: Item not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
  /vaults/{vaultUuid}/items/{itemUuid}:
    get:
      operationId: GetVaultItemById
      tags:
        - Items
      summary: Get the details of an Item
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Vault to fetch Item from
        - in: path
          name: itemUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Item to fetch
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FullItem"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: Item not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                itemNotFound:
                  summary: Item not found
                  value:
                    status: 404
                    message: item {itemUuid} not found
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
    put:
      operationId: UpdateVaultItem
      tags:
        - Items
      summary: Update an Item
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Item's Vault
        - in: path
          name: itemUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Item to update
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FullItem"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FullItem"
        "400":
          description: Unable to create item due to invalid input
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 400
                message: The item doesn't have a {example field name} field.
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: Item not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                itemNotFound:
                  summary: Item not found
                  value:
                    status: 404
                    message: item {itemUuid} not found
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
    delete:
      operationId: DeleteVaultItem
      tags:
        - Items
      summary: Delete an Item
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Vault the item is in
        - in: path
          name: itemUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Item to update
      responses:
        "204":
          description: Successfully deleted an item
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: Item not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
    patch:
      description: >
        Applies a modified [RFC6902 JSON Patch](https://tools.ietf.org/html/rfc6902) document to an Item or ItemField. This endpoint only supports `add`, `remove` and `replace` operations.


        When modifying a specific ItemField, the ItemField's ID in the `path` attribute of the operation object: `/fields/{fieldId}`

      operationId: PatchVaultItem
      tags:
        - Items
      summary: Update a subset of Item attributes
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Vault the item is in
        - in: path
          name: itemUuid
          schema:
            type: string
            pattern: '^[\da-z]{26}$'
          required: true
          description: The UUID of the Item to update
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Patch"
            examples:
              ReplaceAllAttributes:
                value:
                  - op: replace
                    path: "/"
                    value: { "title": "New Title", "favorite": true, "tags": [ "tag1", "tag2" ], "...": "Any attr from FullItem schema" }
                summary: Replace an entire Item with new fields. Equivalent to a PUT request.
              PatchItemAttr:
                value:
                  - op: "replace"
                    path: "/favorite"
                    value: true
                  - op: "remove"
                    path: "/tags/1"
                summary: Update specific Item attributes
              PatchItemField:
                value:
                  - op: "add"
                    path: "/fields"
                    value: { "label": "New Field", "type": "string", "value": "hunter2" }
                summary: Add a new ItemField to the Item
              PatchItemFieldWithID:
                value:
                  - op: "replace"
                    path: "/fields/r9qxq7xnhfhukoxsc8ymqr0y11"
                    value: { "label": "Replacement Title", "type": "string", "value": "new value" }
                  - op: "remove"
                    path: "/fields/h2nl155dshi043yse7wa3u1hs7"
                summary: Modify or remove an ItemField.
              PatchItemFieldAttr:
                value:
                  - op: "add"
                    path: "/fields/s2ju540zlna8bdj4uro7sj64rk/label"
                    value: "New field name"
                  - op: "remove"
                    path: "/fields/s2ju540zlna8bdj4uro7sj64rk/value"
                summary: Modify a specific ItemField attribute.

      responses:
        "200":
          description: OK - Item updated. If no Patch operations were provided, Item is unmodified.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FullItem"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: Item not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                itemNotFound:
                  summary: Item not found
                  value:
                    status: 404
                    message: item {itemUuid} not found
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
  /vaults/{vaultUuid}/items/{itemUuid}/files:
    get:
      operationId: GetItemFiles
      tags:
        - Files
      summary: Get all the files inside an Item
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the Vault to fetch Items from
        - in: path
          name: itemUuid
          schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the Item to fetch files from
        - in: query
          name: inline_files
          schema:
            type: boolean
            example: true
          description: Tells server to return the base64-encoded file contents in the response.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/File"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "404":
          description: Item not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                itemNotFound:
                  summary: Item not found
                  value:
                    status: 404
                    message: item {itemUuid} not found
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
        "413":
          description: File content too large to display
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                fileTooLarge:
                  summary: File too large
                  value:
                    status: 413
                    message: File is too large to inline in request. Use the /v1/vaults/{vaultUUID}/items/{itemUUID}/files/{fileUUID}/content endpoint instead.
  /vaults/{vaultUuid}/items/{itemUuid}/files/{fileUuid}:
    get:
      operationId: GetDetailsOfFileById
      tags:
        - Files
      summary: Get the details of a File
      security:
        - ConnectToken: [ ]
      parameters:
        - in: path
          name: vaultUuid
          schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the Vault to fetch Item from
        - in: path
          name: itemUuid
          schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the Item to fetch File from
        - in: path
          name: fileUuid
          schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the File to fetch
        - in: query
          name: inline_files
          schema:
            type: boolean
            example: true
          description: Tells server to return the base64-encoded file contents in the response.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/File"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "403":
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 403
                message: vault {vaultUuid} is not in scope
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                fileNotFound:
                  summary: File not found
                  value:
                    status: 404
                    message: file {fileUuid} not found
                itemNotFound:
                  summary: Item not found
                  value:
                    status: 404
                    message: item {itemUuid} not found
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
        "413":
          description: File content too large to display
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                fileTooLarge:
                  summary: File too large
                  value:
                    status: 413
                    message: File is too large to inline in request. Use the /v1/vaults/{vaultUUID}/items/{itemUUID}/files/{fileUUID}/content endpoint instead.
  /vaults/{vaultUuid}/items/{itemUuid}/files/{fileUuid}/content:
    parameters:
      - in: path
        name: vaultUuid
        schema:
          type: string
          format: uuid
        required: true
        description: The UUID of the Vault the item is in
      - in: path
        name: itemUuid
        schema:
          type: string
          format: uuid
        required: true
        description: The UUID of the Item the File is in
      - in: path
        name: fileUuid
        required: true
        schema:
          type: string
        description: UUID of the file to get content from
    get:
      operationId: DownloadFileByID
      tags:
        - Files
      summary: Get the content of a File
      security:
        - ConnectToken: [ ]
      responses:
        "200":
          description: "Success"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="privkey.pem"
            Content-Length:
              schema:
                type: string
                example: "6432"
        "401":
          description: Invalid or missing token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                status: 401
                message: Invalid token signature
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                fileNotFound:
                  summary: File not found
                  value:
                    status: 404
                    message: file {fileUuid} not found
                itemNotFound:
                  summary: Item not found
                  value:
                    status: 404
                    message: item {itemUuid} not found
                vaultNotFound:
                  summary: Vault not found
                  value:
                    status: 404
                    message: vault {vaultUuid} not found
  /heartbeat:
    get:
      operationId: GetHeartbeat
      tags:
        - Health
      summary: Ping the server for liveness
      servers:
        - url: http://localhost:8080
      responses:
        "200":
          description: OK
          content:
            text/plain:
              schema:
                type: string
                example: .
  /health:
    get:
      operationId: GetServerHealth
      tags:
        - Health
      summary: Get state of the server and its dependencies.
      servers:
        - url: http://localhost:8080
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [ "name", "version" ]
                properties:
                  name:
                    type: string
                  version:
                    type: string
                    description: The Connect server's version
                  dependencies:
                    type: array
                    items:
                      $ref: "#/components/schemas/ServiceDependency"
              examples:
                WaitingForAPIRequest:
                  value:
                    name: 1Password Connect API
                    version: 1.2.1
                    dependencies:
                      - service: sync
                        status: "TOKEN_NEEDED"
                      - service: sqlite
                        status: "ACTIVE"
                        message: "Connected to./1password.sqlite"
                  summary: API server waiting for first authenticated request

  /metrics:
    get:
      operationId: GetPrometheusMetrics
      tags:
        - Metrics
      description: See Prometheus documentation for a complete data model.
      summary: Query server for exposed Prometheus metrics
      servers:
        - url: http://localhost:8080
      responses:
        "200":
          description: Successfully returned Prometheus metrics
          content:
            text/plain:
              schema:
                type: string
                example: |
                  # HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
                  # TYPE go_gc_duration_seconds summary
                  go_gc_duration_seconds{quantile="0"} 2.9153e-05
                  go_gc_duration_seconds{quantile="0.25"} 6.2832e-05
                  go_gc_duration_seconds{quantile="0.5"} 9.7187e-05
                  go_gc_duration_seconds{quantile="0.75"} 0.000112967
                  go_gc_duration_seconds{quantile="1"} 0.000215819
                  go_gc_duration_seconds_sum 0.001376862
                  go_gc_duration_seconds_count 14


================================================
FILE: examples/aws-ecs-fargate/README.md
================================================
# AWS ECS with Fargate

The CloudFormation document in this folder will create the following resources, along with the necessary rules, groups, and policies to make them work.

The result is a publicly accessible hostname (via HTTPS) that routes requests to a 1Password Connect instance running in AWS Fargate. You can use your own VPC and subnets, or optionally set up DNS records automatically with Route 53. 

To customize further, like deploying into an existing ECS cluster, use this CloudFormation as a starting point and tweak it with a text editor or Amazon's CloudFormation Designer tool.

## Networking Resources

- An ACM certificate for HTTPS
- An ALB (Application Load Balancer) with HTTPS
- A VPC (optional, if you don’t provide your own)
- 2 Public subnets (optional, if you don’t provide your own)
- An Internet Gateway (optional, if creating a VPC)
- Route 53 DNS records (optional, if you provide a hosted zone ID)

## ECS Resources

- An ECS Cluster
- Task Definition
  - `1password/connect-api` container
  - `1password/connect-sync` container
- Service Discovery for internal routing

## Getting Started

When [importing this CloudFormation template](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-template.html), you’ll be prompted to provide the following:

- **CredentialsJson**: The plain JSON contents of your `1password-credentials.json` file.  The template will encode this to base64 and store it securely in AWS Secrets Manager.
- **DomainName**: A fully qualified domain name for the ALB (e.g., `connect.example.com`). This is required for HTTPS.
- **Route53HostedZoneID** (optional): If you’re using Route 53, provide the hosted zone ID to automatically create DNS records. Leave blank to set up DNS manually with your registrar.
- **VPCID**, **PublicSubnets** (optional): Provide these if you want to use an existing VPC and subnets. Leave as default to create a new VPC. 
- **VPCCIDR** (optional): Sets the CIDR for a new VPC if you’re creating one; ignored if using an existing VPC.

### DNS Setup (If Not Using Route 53)
If you leave `Route53HostedZoneID` blank, the deployment will pause at the `HTTPSCertificate` creation step, waiting for DNS validation:
1. Check the ACM console for the certificate (status: `Pending validation`).
2. Add the provided CNAME record to your DNS registrar to validate the certificate.
3. Wait for the certificate to be issued (status: `Issued`), then the deployment will continue.
4. After deployment, use the `ExternalUrl` output (or `CNAME Name` and `CNAME Value`) to create an `A` (with alias) or `CNAME` record in your registrar to point to the ALB.

For more details, see the deployment note in the template description.

================================================
FILE: examples/aws-ecs-fargate/connect-server.yaml
================================================
AWSTemplateFormatVersion: 2010-09-09
Description: >-
  Sets up 1Password Connect on Fargate with an ALB for HTTPS ingress, in a public VPC subnet.
  Note: If you leave Route53HostedZoneID blank, deployment will pause at HTTPSCertificate creation.
  Check the ACM console for a CNAME record to add to your DNS registrar for validation, then wait for the certificate to be issued.

# Makes the params look nice in the AWS console
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Network & Credentials Setup
        Parameters:
          - VPCID
          - VPCCIDR
          - PublicSubnets
          - Route53HostedZoneID
          - DomainName
          - CredentialsJson
    ParameterLabels:
      VPCID:
        default: VPC ID
      VPCCIDR:
        default: VPC CIDR
      PublicSubnets:
        default: Public Subnets
      Route53HostedZoneID:
        default: Route 53 Hosted Zone
      DomainName:
        default: 1Password Connect Server domain name
      CredentialsJson:
        default: 1Password Credentials JSON

# Stuff you can tweak when you deploy
Parameters:
  VPCID:
    Type: String
    Description: Got a VPC ID? Toss it here, or leave it blank to make a new one
    Default: ""
  VPCCIDR:
    Type: String
    Description: CIDR for a new VPC if we're making one
    Default: 10.0.0.0/16
  PublicSubnets:
    Type: CommaDelimitedList
    Description: List of public subnet IDs, need at least 2 if you're using an existing VPC
    Default: ""
  Route53HostedZoneID:
    Type: String
    Description: Route 53 hosted zone ID for creating DNS records (leave empty to set up manually)
    Default: ""
  DomainName:
    Type: String
    Description: Domain name for the ALB (e.g., connect.example.com), must be in Route 53 if hosted zone is provided
    MinLength: 1
    ConstraintDescription: Need to provide a domain name for HTTPS
  CredentialsJson:
    Type: String
    Description: Your 1password-credentials.json as plain JSON
    NoEcho: true
    MinLength: 1
    ConstraintDescription: gotta have something here

# Check if we need to whip up a new VPC or create DNS records
Conditions:
  CreateVPC: !Equals [!Ref VPCID, ""]
  CreateRoute53Records: !Not [!Equals [!Ref Route53HostedZoneID, ""]]
  OutputDNSRecordContent: !Not [Condition: CreateRoute53Records]

# Predefined CIDRs for the VPC and subnets
Mappings:
  SubnetConfig:
    VPC:
      CIDR: '10.0.0.0/16'
    PublicOne:
      CIDR: '10.0.1.0/24'
    PublicTwo:
      CIDR: '10.0.2.0/24'

Resources:
  # Secret to store the base64-encoded credentials
  CredentialsSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub 'CredentialsSecret-${AWS::StackName}'
      SecretString: !Base64 { "Fn::Sub": "${CredentialsJson}" }

  # VPC stuff if we need to make one
  VPC:
    Condition: CreateVPC
    Type: AWS::EC2::VPC
    Properties:
      EnableDnsSupport: true
      EnableDnsHostnames: true
      CidrBlock: !Ref VPCCIDR
  PublicSubnetOne: # first subnet for the VPC
    Condition: CreateVPC
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [0, !GetAZs '']
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
      MapPublicIpOnLaunch: true
  PublicSubnetTwo: # second subnet for the VPC
    Condition: CreateVPC
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [1, !GetAZs '']
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR']
      MapPublicIpOnLaunch: true
  InternetGateway: # need this for internet access in the new VPC
    Condition: CreateVPC
    Type: AWS::EC2::InternetGateway
  GatewayAttachement:
    Condition: CreateVPC
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  PublicRouteTable:
    Condition: CreateVPC
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  PublicRoute: # sets up the route to the internet
    Condition: CreateVPC
    DependsOn: GatewayAttachement
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGateway
  PublicSubnetOneRouteTableAssociation:
    Condition: CreateVPC
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetOne
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetTwoRouteTableAssociation:
    Condition: CreateVPC
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetTwo
      RouteTableId: !Ref PublicRouteTable

  # ECS cluster to run stuff
  ECSCluster:
    Type: AWS::ECS::Cluster

  # SGs for Fargate and ALB
  LoadBalancerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Access to the public facing load balancer
      VpcId: !If [CreateVPC, !Ref VPC, !Ref VPCID]
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: HTTPS from anywhere
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          CidrIp: !If
            - CreateVPC
            - !GetAtt VPC.CidrBlock
            - 0.0.0.0/0
  FargateContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Access to the Fargate containers
      VpcId: !If [CreateVPC, !Ref VPC, !Ref VPCID]
  EcsSecurityGroupIngressFromPublicALB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Ingress from the public ALB
      GroupId: !Ref FargateContainerSecurityGroup
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      SourceSecurityGroupId: !Ref LoadBalancerSG
  EcsSecurityGroupIngressFromSelf:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Ingress for sync container communication
      GroupId: !Ref FargateContainerSecurityGroup
      IpProtocol: tcp
      FromPort: 8081
      ToPort: 8081
      SourceSecurityGroupId: !Ref FargateContainerSecurityGroup
  EcsSecurityGroupEgress:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref FargateContainerSecurityGroup
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: 0.0.0.0/0
      Description: HTTPS to external services (e.g., Docker Hub)

  # ALB setup
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '30'
      Subnets: !If
        - CreateVPC
        - [!Ref PublicSubnetOne, !Ref PublicSubnetTwo]
        - !Ref PublicSubnets
      SecurityGroups: [!Ref LoadBalancerSG]

  HTTPSCertificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainName
      ValidationMethod: DNS
      DomainValidationOptions: !If
        - CreateRoute53Records
        - - DomainName: !Ref DomainName
            HostedZoneId: !Ref Route53HostedZoneID
        - !Ref AWS::NoValue

  HTTPSListener:
    DependsOn:
      - LoadBalancer
      - HTTPSCertificate
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref ConnectTargetGroup
          Type: forward
      LoadBalancerArn: !Ref LoadBalancer
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-TLS13-1-2-Res-2021-06
      Certificates:
        - CertificateArn: !Ref HTTPSCertificate

  ConnectTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn:
      - LoadBalancer
    Properties:
      HealthCheckIntervalSeconds: 6
      HealthCheckPath: /heartbeat
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: ip
      Name: connect
      Port: 8080
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      VpcId: !If [CreateVPC, !Ref VPC, !Ref VPCID]
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60

  # DNS record for the ALB (if using Route 53)
  DNSRecord:
    Condition: CreateRoute53Records
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref Route53HostedZoneID
      Comment: DNS record pointing to load balancer for 1Password Connect
      Name: !Ref DomainName
      Type: A
      AliasTarget:
        DNSName: !GetAtt LoadBalancer.DNSName
        HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID

  # Service discovery for internal routing
  ServiceDiscoveryNamespace:
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
    Properties:
      Description: Private DNS namespace for 1Password Connect
      Vpc: !If [CreateVPC, !Ref VPC, !Ref VPCID]
      Name: onepassword

  ConnectServiceDiscovery:
    Type: AWS::ServiceDiscovery::Service
    DependsOn:
      - ServiceDiscoveryNamespace
    Properties:
      DnsConfig:
        DnsRecords:
          - TTL: 60
            Type: SRV
      Name: connect
      NamespaceId: !Ref ServiceDiscoveryNamespace
      HealthCheckCustomConfig:
        FailureThreshold: 1

  # IAM roles for ECS
  ECSRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ECSFargatePermissions
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:AttachNetworkInterface
                  - ec2:CreateNetworkInterface
                  - ec2:DeleteNetworkInterface
                  - ec2:DescribeNetworkInterfaces
                  - ec2:DetachNetworkInterface
                  - elasticloadbalancing:RegisterTargets
                  - elasticloadbalancing:DeregisterTargets
                  - elasticloadbalancing:DescribeTargetHealth
                Resource: '*'

  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AmazonECSTaskExecutionRolePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: '*'
        - PolicyName: SecretAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:CredentialsSecret-${AWS::StackName}*'

  # Logs in CloudWatch
  CloudWatchLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: onepassword-connect
      RetentionInDays: 30

  # Task def for the Connect containers
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    DependsOn:
      - ECSTaskExecutionRole
      - CloudWatchLogsGroup
    Properties:
      Cpu: 256
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      TaskRoleArn: !Ref ECSRole
      Volumes:
        - Name: connect-data
      ContainerDefinitions:
        - Name: connect-api
          Image: 1password/connect-api:latest
          PortMappings:
            - ContainerPort: 8080
              Protocol: tcp
          MountPoints:
            - ContainerPath: /home/opuser/.op/data
              SourceVolume: connect-data
          Secrets:
            - Name: OP_SESSION
              ValueFrom: !Ref CredentialsSecret
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref CloudWatchLogsGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: connect-api
        - Name: connect-sync
          Image: 1password/connect-sync:latest
          MountPoints:
            - ContainerPath: /home/opuser/.op/data
              SourceVolume: connect-data
          Environment:
            - Name: OP_HTTP_PORT
              Value: '8081'
          Secrets:
            - Name: OP_SESSION
              ValueFrom: !Ref CredentialsSecret
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref CloudWatchLogsGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: connect-sync

  # ECS service to run the containers
  ConnectService:
    Type: AWS::ECS::Service
    DependsOn:
      - LoadBalancer
      - HTTPSListener
    Properties:
      ServiceName: connect
      Cluster: !Ref ECSCluster
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref FargateContainerSecurityGroup
          Subnets: !If
            - CreateVPC
            - [!Ref PublicSubnetOne, !Ref PublicSubnetTwo]
            - !Ref PublicSubnets
      TaskDefinition: !Ref TaskDefinition
      LoadBalancers:
        - ContainerName: connect-api
          ContainerPort: 8080
          TargetGroupArn: !Ref ConnectTargetGroup
      ServiceRegistries:
        - ContainerName: connect-api
          ContainerPort: 8080
          RegistryArn: !GetAtt ConnectServiceDiscovery.Arn

# Stuff you'll need after deployment
Outputs:
  CNAMEName:
    Condition: OutputDNSRecordContent
    Description: Name of a DNS record to point to the load balancer (use with CNAME Value for manual setup)
    Value: !Sub ${DomainName}.
  CNAMEValue:
    Condition: OutputDNSRecordContent
    Description: Value of a DNS record to point to the load balancer (use with CNAME Name for manual setup)
    Value: !Sub ${LoadBalancer.DNSName}.
  ExternalUrl:
    Description: The URL of the external load balancer
    Value: !Join ['', ['https://', !GetAtt LoadBalancer.DNSName]]
    Export:
      Name: !Join [':', [!Ref AWS::StackName, 'ExternalUrl']]
      

================================================
FILE: examples/beta/aws-ecs-apigateway-cfn/README.md
================================================
# AWS ECS with Fargate and API Gateway

The CloudFormation document in this folder will create the following resources, along with the necessary rules, groups, and policies to make them work.

The result is a publicly accessible hostname (via HTTPS) that routes requests to a 1Password Connect instance running in AWS Fargate, using API Gateway for secure ingress. You can use your own VPC and subnets.

To customize further, like deploying into an existing ECS cluster, use this CloudFormation as a starting point and tweak it with a text editor or Amazon's CloudFormation Designer tool.

## Networking Resources

- A VPC Link for API Gateway to route to the Fargate service
- An API Gateway with HTTPS
- A VPC (optional, if you don’t provide your own)
- 2 Public subnets (optional, if you don’t provide your own)
- An Internet Gateway (optional, if creating a VPC)


## ECS Resources

- An ECS Cluster
- Task Definition
  - `1password/connect-api` container
  - `1password/connect-sync` container
- Service Discovery for internal routing

## Getting Started

When [importing this CloudFormation template](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-template.html), you’ll be prompted to provide the following:

- **CredentialsJson**: The plain JSON contents of your `1password-credentials.json` file (e.g., `{"token":"your-token"}`). The template will encode this to base64 and store it securely in AWS Secrets Manager.
- **VPCID**, **PublicSubnets** (optional): Provide these if you want to use an existing VPC and subnets. Leave as default to create a new VPC.
- **VPCCIDR** (optional): Sets the CIDR for a new VPC if you’re creating one; ignored if using an existing VPC.

For more details, see the deployment note in the template description.

================================================
FILE: examples/beta/aws-ecs-apigateway-cfn/connect-server.yaml
================================================
AWSTemplateFormatVersion: 2010-09-09
Description: Sets up 1Password Connect on Fargate with API Gateway, stores 1password-credentials.json in Secrets Manager, and makes a VPC if you don’t give one

# Makes the params look nice in the AWS console
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Network & Credentials Setup
        Parameters:
          - VPCID
          - VPCCIDR
          - PublicSubnets
          - CredentialsJson
    ParameterLabels:
      CredentialsJson:
        default: 1Password Credentials JSON
      VPCID:
        default: VPC ID
      VPCCIDR:
        default: VPC CIDR
      PublicSubnets:
        default: Public Subnets

# Parameters you can tweak when you deploy
Parameters:
  VPCID:
    Type: String
    Description: Got a VPC ID? Toss it here, or leave it blank to make a new one
    Default: ""
  VPCCIDR:
    Type: String
    Description: CIDR for a new VPC if we're making one
    Default: 10.0.0.0/16
  PublicSubnets:
    Type: CommaDelimitedList
    Description: List of public subnet IDs, need at least 2 if you're using an existing VPC
    Default: ""
  CredentialsJson:
    Type: String
    Description: Your 1password-credentials.json as plain JSON
    NoEcho: true
    MinLength: 1
    ConstraintDescription: gotta have something here

# Check if we need to whip up a new VPC
Conditions:
  CreateVPC:
    Fn::Equals:
      - !Ref VPCID
      - ""

# Predefined CIDRs for the VPC and subnets
Mappings:
  SubnetConfig:
    VPC:
      CIDR: '10.0.0.0/16'
    PublicOne:
      CIDR: '10.0.1.0/24'
    PublicTwo:
      CIDR: '10.0.2.0/24'

Resources:
  # Secret to store the base64-encoded credentials
  CredentialsSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub 'CredentialsSecret-${AWS::StackName}'
      SecretString: !Base64 { "Fn::Sub": "${CredentialsJson}" }

  # VPC stuff if we need to make one
  VPC:
    Condition: CreateVPC
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true

  PublicSubnetOne:
    Condition: CreateVPC
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ''
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
      MapPublicIpOnLaunch: true

  PublicSubnetTwo:
    Condition: CreateVPC
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ''
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR']
      MapPublicIpOnLaunch: true

  InternetGateway:
    Condition: CreateVPC
    Type: AWS::EC2::InternetGateway

  GatewayAttachment:
    Condition: CreateVPC
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Condition: CreateVPC
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PublicRoute:
    Condition: CreateVPC
    DependsOn: GatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGateway

  PublicSubnetOneRouteTableAssociation:
    Condition: CreateVPC
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetOne
      RouteTableId: !Ref PublicRouteTable

  PublicSubnetTwoRouteTableAssociation:
    Condition: CreateVPC
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetTwo
      RouteTableId: !Ref PublicRouteTable

  # ECS cluster to run stuff
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      CapacityProviders:
        - FARGATE
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE
          Weight: 1

  # SGs for Fargate and API Gateway
  FargateContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Access to the Fargate containers
      VpcId: !If [CreateVPC, !Ref VPC, !Ref VPCID]

  ApiGatewaySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: API Gateway traffic for 1Password Connect
      VpcId: !If [CreateVPC, !Ref VPC, !Ref VPCID]

  # Security group rules
  ApiGatewayEgress: # lets API Gateway talk to Fargate
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref ApiGatewaySecurityGroup
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      DestinationSecurityGroupId: !Ref FargateContainerSecurityGroup

  ConnectIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref FargateContainerSecurityGroup
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      SourceSecurityGroupId: !Ref ApiGatewaySecurityGroup

  ConnectSelfIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Ingress from other containers in the same security group
      GroupId: !Ref FargateContainerSecurityGroup
      IpProtocol: -1
      SourceSecurityGroupId: !Ref FargateContainerSecurityGroup

  ConnectPublicEgress:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref FargateContainerSecurityGroup
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: 0.0.0.0/0
      Description: HTTPS to external services (e.g., Docker Hub)

  # IAM roles for ECS
  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AmazonECSTaskExecutionRolePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: '*'
        - PolicyName: SecretAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:CredentialsSecret-${AWS::StackName}*'

  ECSRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ECSPermissions
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:AttachNetworkInterface
                  - ec2:CreateNetworkInterface
                  - ec2:DeleteNetworkInterface
                  - ec2:DescribeNetworkInterfaces
                  - ec2:DetachNetworkInterface
                Resource: '*'

  # Logs in CloudWatch
  CloudWatchLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: onepassword-connect
      RetentionInDays: 30

  # Service discovery for internal routing
  ServiceDiscoveryNamespace:
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
    Properties:
      Description: Private DNS namespace for 1Password Connect
      Vpc: !If [CreateVPC, !Ref VPC, !Ref VPCID]
      Name: onepassword

  ConnectServiceDiscovery:
    Type: AWS::ServiceDiscovery::Service
    DependsOn:
      - ServiceDiscoveryNamespace
    Properties:
      DnsConfig:
        DnsRecords:
          - TTL: 60
            Type: SRV
      Name: connect
      NamespaceId: !Ref ServiceDiscoveryNamespace
      HealthCheckCustomConfig:
        FailureThreshold: 1

  # Task def for the Connect containers
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    DependsOn:
      - ECSTaskExecutionRole
      - CredentialsSecret
      - CloudWatchLogsGroup
    Properties:
      Cpu: 256
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      TaskRoleArn: !Ref ECSRole
      Volumes:
        - Name: data
      ContainerDefinitions:
        - Name: connect-api
          Image: 1password/connect-api:latest
          PortMappings:
            - ContainerPort: 8080
              Protocol: tcp
          MountPoints:
            - ContainerPath: /home/opuser/.op/data
              SourceVolume: data
          Secrets:
            - Name: OP_SESSION
              ValueFrom: !Ref CredentialsSecret
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref CloudWatchLogsGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: connect-api
        - Name: connect-sync
          Image: 1password/connect-sync:latest
          MountPoints:
            - ContainerPath: /home/opuser/.op/data
              SourceVolume: data
          Environment:
            - Name: OP_HTTP_PORT
              Value: '8081'
          Secrets:
            - Name: OP_SESSION
              ValueFrom: !Ref CredentialsSecret
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref CloudWatchLogsGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: connect-sync

  # ECS service to run the containers
  ConnectService:
    Type: AWS::ECS::Service
    DependsOn:
      - TaskDefinition
      - ApiGatewayStage
      - ConnectServiceDiscovery
      - FargateContainerSecurityGroup
    Properties:
      ServiceName: connect
      Cluster: !Ref ECSCluster
      LaunchType: FARGATE
      DesiredCount: 1
      TaskDefinition: !Ref TaskDefinition
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref FargateContainerSecurityGroup
          Subnets: !If
            - CreateVPC
            - [!Ref PublicSubnetOne, !Ref PublicSubnetTwo]
            - !Ref PublicSubnets
      ServiceRegistries:
        - ContainerName: connect-api
          ContainerPort: 8080
          RegistryArn: !GetAtt ConnectServiceDiscovery.Arn

  # API Gateway setup
  VpcLink:
    Type: AWS::ApiGatewayV2::VpcLink
    Properties:
      Name: connect-vpc-link
      SecurityGroupIds:
        - !Ref ApiGatewaySecurityGroup
      SubnetIds: !If
        - CreateVPC
        - [!Ref PublicSubnetOne, !Ref PublicSubnetTwo]
        - !Ref PublicSubnets

  ApiGateway:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: onepassword-connect
      ProtocolType: HTTP
      Description: API Gateway for 1Password Connect

  ApiGatewayIntegration:
    Type: AWS::ApiGatewayV2::Integration
    DependsOn:
      - ApiGateway
      - VpcLink
    Properties:
      ApiId: !Ref ApiGateway
      IntegrationType: HTTP_PROXY
      IntegrationMethod: ANY
      ConnectionType: VPC_LINK
      ConnectionId: !Ref VpcLink
      IntegrationUri: !GetAtt ConnectServiceDiscovery.Arn
      PayloadFormatVersion: 1.0

  ApiGatewayRoute:
    Type: AWS::ApiGatewayV2::Route
    DependsOn:
      - ApiGateway
      - ApiGatewayIntegration
    Properties:
      ApiId: !Ref ApiGateway
      RouteKey: $default
      AuthorizationType: NONE
      Target: !Join
        - /
        - - integrations
          - !Ref ApiGatewayIntegration

  ApiGatewayStage:
    Type: AWS::ApiGatewayV2::Stage
    DependsOn:
      - ApiGateway
      - ApiGatewayRoute
    Properties:
      StageName: $default
      ApiId: !Ref ApiGateway
      AutoDeploy: true

Outputs:
  ExternalUrl:
    Description: The URL of the API Gateway for 1Password Connect
    Value: !GetAtt ApiGateway.ApiEndpoint
    Export:
      Name: !Join [ ':', [ !Ref AWS::StackName, 'ExternalUrl' ] ]
      

================================================
FILE: examples/beta/azure-container-apps-arm/README.md
================================================
# Deploy 1Password Connect Server on Azure Container Apps using the Azure Portal

_Learn how to deploy 1Password Connect Server on the [Azure Container Apps](https://azure.microsoft.com/en-us/products/container-apps/#overview) service._

This deployment consists of two [containers](https://learn.microsoft.com/en-us/azure/container-apps/containers): One for the Connect API and another for Connect Sync. There's also an [ingress](https://learn.microsoft.com/en-us/azure/container-apps/ingress-overview) for the API container. There are a few benefits to deploying 1Password Connect Server on Azure Container Apps:

- **Low cost:** For standard deployments, the service will host your Connect Server for ~$16 USD/month (as of January 2024). Container Apps pricing is variable based on activity, and you can learn more on [Microsoft's pricing page](https://azure.microsoft.com/en-us/pricing/details/container-apps/).
- **Automatic DNS record management:** You don't need to manage a DNS record. Azure Container Apps automatically provides a unique one for your Connect Server domain.
- **Automatic TLS certificate management:** Azure Container Apps automatically handles TLS certificate management on your behalf.

## Before you begin

Before you begin, ensure you have an Azure account with permission to create a Container App. You'll also need the `1password-credentials.json` file for your 1Password Connect Server, which contains the credentials for authenticating the server.

> [!NOTE]
> If you don't have an Azure account, you can sign up for a free trial with starting credit: https://azure.microsoft.com/free/

## Step 1: Download the template file

1. Download the [ARM(Azure Resource Manager) template](./aca-op-connect-server-template.json) file using the download icon at the top right.

## Step 2: Create the Container App

1. Sign in to the Azure Portal and go to the [Deploy a custom template](https://portal.azure.com/#create/Microsoft.Template) page.
2. Click **Build your own template in the editor**.
3. Click **Load file** and upload the template file you downloaded earlier. Then click **Save**.
4. Fill out the following fields:
   - **Subscription** : Choose the subscription you prefer.
   - **Resource group**: Choose an existing Resource Group or create a new one using **Create new** button.
   - **Region**: Choose the region you prefer.
   - **Container App Name**: Enter a name you'd like to use, default will be `op-connect-con-app`.
   - **Container App Env Name**: Enter a name you'd like to use, default will be `op-connect-con-env`.
   - **Container App Log Analytics Name**: Enter a name you'd like to use, default will be `op-connect-con-app-log-analytics`.
   - **Credentials** : Paste the entire contents of the `1password-credentials.json` file.
5. Click **Review + create**.
6. Once the validation succeeds, click **Create**. It is expected to take a couple of minutes to complete the deployment.

## Step 3: Test your Connect Server

Once your deployment is complete, click **Go to resource group** and click on the container app you created.

To test if your Connect Server is online, choose **Overview** in your application's sidebar, then copy your **Application Url** link. This is your **Connect Server URL**. Use your 1Password Connect access token to interact with the API (e.g., via a `curl` request or a client application) to verify that your Connect Server is connected to your 1Password account.

```sh
curl --silent --show-error --request GET --header "Accept: application/json" \
  --header "Authorization: Bearer mF_9.B5f-4.1JqM" \
  https://op-connect.example.com/v1/vaults
```

<details>
<summary>Example JSON response:</summary>

```json
[
  {
    "attributeVersion": 1,
    "contentVersion": 9,
    "createdAt": "2025-03-21T13:06:35Z",
    "id": "hgm3sn37vkj3lsdkeouq46wyca",
    "items": 5,
    "name": "REDACTED",
    "type": "USER_CREATED",
    "updatedAt": "2025-04-18T16:38:40Z"
  },
  {
    "attributeVersion": 1,
    "contentVersion": 2,
    "createdAt": "2025-03-21T02:15:33Z",
    "id": "mn4w65hq6ar7nc2fc7qgoru3ki",
    "items": 1,
    "name": "REDACTED",
    "type": "USER_CREATED",
    "updatedAt": "2025-03-21T02:16:32Z"
  },
  {
    "attributeVersion": 1,
    "contentVersion": 94,
    "createdAt": "2025-03-21T15:20:41Z",
    "id": "z4s56cbejab6q6urod3l5icqky",
    "items": 3,
    "name": "REDACTED",
    "type": "USER_CREATED",
    "updatedAt": "2025-04-29T02:49:13Z"
  }
]
```

</details>
<br />

## Step 4: Integrate with your application

To use the Connect Server, configure your application to communicate with the Connect Server URL using the access token provided in your 1Password account. Follow the [1Password Connect integration guide](https://developer.1password.com/docs/connect) to set up your application.

<hr>

## Update your Connect Server in the Azure Portal

> [!TIP]
> Check for 1Password Connect Server updates on the [1Password releases page](https://releases.1password.com/).

1. Within your deployed 1Password Connect Server Container App in the [Azure Container Apps Portal](https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.App%2FcontainerApps), select **Containers** from the sidebar.
2. Click **Edit and deploy**.
3. Select the checkbox next to your **connect-api** container, then choose **Edit**.
4. Change the version number in the **Image and Tag** field, `1password/connect-api:latest`, to match the latest version from our [1Password releases page](https://releases.1password.com/).
5. Repeat for the **connect-sync** container, updating `1password/connect-sync:latest` to the same version.
6. Select **Save**.
7. Select **Create** to deploy a new revision using the updated images.
8. Test your Connect Server URL with your access token to verify the update.

## Get help

### How to update the **credentials** secret

To use a new `1password-credentials.json` credentials file for your Connect Server, replace the secret in your Container App:

<details>
<summary>Replace your <code>credentials</code> secret using the Azure Portal</summary>

1. Open the Azure Portal and go to the [Container Apps](https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.App%2FcontainerApps) page.
2. Choose **Secrets** from the Settings section in the sidebar.
3. Edit the **credentials** secret and paste the entire contents of your new `1password-credentials.json` file.
4. Select the checkbox and click **Save**.
5. Choose the **Revisions** from the Application section in the sidebar.
6. Click your current active revision and choose **Restart** in the details pane.
7. Test your Connect Server URL with your new access token to [test your Connect Server](#step-3-test-your-connect-server).
</details>

================================================
FILE: examples/beta/azure-container-apps-arm/aca-op-connect-server-template.json
================================================
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "logAnalyticsWorkspaceName": {
      "type": "string",
      "defaultValue": "op-connect-con-app-log-analytics",
      "metadata": {
        "description": "The name of the Log Analytics Workspace."
      }
    },
    "containerAppName": {
      "type": "string",
      "defaultValue": "op-connect-con-app",
      "metadata": {
        "description": "The name of the Container App."
      }
    },
    "containerAppEnvName": {
      "type": "string",
      "defaultValue": "op-connect-con-env",
      "metadata": {
        "description": "The name of the Container App Environment."
      }
    },
    "credentials": {
      "type": "SecureString",
      "metadata": {
        "description": "The plain text contents of 1password-credentials.json."
      }
    }
  },
  "resources": [
    {
      "type": "Microsoft.OperationalInsights/workspaces",
      "apiVersion": "2022-10-01",
      "name": "[parameters('logAnalyticsWorkspaceName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "sku": {
          "name": "PerGB2018"
        }
      }
    },
    {
      "type": "Microsoft.App/managedEnvironments",
      "apiVersion": "2023-05-01",
      "name": "[parameters('containerAppEnvName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]"
      ],
      "properties": {
        "appLogsConfiguration": {
          "destination": "log-analytics",
          "logAnalyticsConfiguration": {
            "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))).customerId]",
            "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').primarySharedKey]"
          }
        },
        "zoneRedundant": false
      }
    },
    {
      "type": "Microsoft.App/containerApps",
      "apiVersion": "2023-05-01",
      "name": "[parameters('containerAppName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppEnvName'))]"
      ],
      "properties": {
        "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppEnvName'))]",
        "configuration": {
          "secrets": [
            {
              "name": "credentials",
              "value": "[parameters('credentials')]"
            }
          ],
          "activeRevisionsMode": "Single",
          "ingress": {
            "external": true,
            "targetPort": 8080,
            "allowInsecure": false,
            "traffic": [
              {
                "latestRevision": true,
                "weight": 100
              }
            ]
          }
        },
        "template": {
          "containers": [
            {
              "name": "connect-api",
              "image": "docker.io/1password/connect-api:latest",
              "resources": {
                "cpu": 0.25,
                "memory": "0.5Gi"
              },
              "volumeMounts": [
                {
                  "volumeName": "data",
                  "mountPath": "/home/opuser/.op/data"
                },
                {
                  "volumeName": "credentials",
                  "mountPath": "/home/opuser/.op/1password-credentials.json",
                  "subPath": "1password-credentials.json"
                }
              ]
            },
            {
              "name": "connect-sync",
              "image": "docker.io/1password/connect-sync:latest",
              "resources": {
                "cpu": 0.25,
                "memory": "0.5Gi"
              },
              "env": [
                {
                  "name": "OP_HTTP_PORT",
                  "value": "8081"
                }
              ],
              "volumeMounts": [
                {
                  "volumeName": "data",
                  "mountPath": "/home/opuser/.op/data"
                },
                {
                  "volumeName": "credentials",
                  "mountPath": "/home/opuser/.op/1password-credentials.json",
                  "subPath": "1password-credentials.json"
                }
              ]
            }
          ],
          "volumes": [
            {
              "name": "data",
              "storageType": "EmptyDir"
            },
            {
              "name": "credentials",
              "storageType": "Secret",
              "secrets": [
                {
                  "secretRef": "credentials",
                  "path": "1password-credentials.json"
                }
              ]
            }
          ],
          "scale": {
            "minReplicas": 1,
            "maxReplicas": 1
          }
        }
      }
    }
  ]
}

================================================
FILE: examples/beta/docker/README.md
================================================
# [Beta] Deploy 1Password Connect Server using Docker Swarm

This example describes how to deploy 1Password Connect Server as a [stack](https://docs.docker.com/engine/swarm/stack-deploy/) using [Docker Swarm](https://docs.docker.com/engine/swarm/key-concepts/#what-is-a-swarm). The stack includes two [services](https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/) (one each for the Connect API and SYNC containers), a [Docker secret](https://docs.docker.com/engine/swarm/secrets/) for the `1password-credentials.json` file, and optional secrets and configuration required only for using a custom TLS certificate.

## In this folder

- [`README.md`](./README.md): the document that you are reading. 👋😃
- [`compose.template.yaml`](./compose.template.yaml): a [Compose Specification](https://docs.docker.com/compose/compose-file/) format [Compose file](https://docs.docker.com/compose/compose-file/03-compose-file/) for 1Password Connect Server
- [`compose.lets-encrypt.yaml`](./compose.lets-encrypt.yaml): an [override configuration](https://docs.docker.com/compose/multiple-compose-files/merge/) to merge the configuration necessary for Let's Encrypt
- [`compose.http.yaml`](./compose.http.yaml): optional configuration for exposing an HTTP port on the Docker host for forwarding plain-text traffic within a private network from a public TLS termination endpoint
- [`compose.custom-tls.yaml`](./compose.custom-tls.yaml): optional configuration for enabling a self-managed TLS certificate
- [`connect-api.env`](./connect-api.env): an environment file used to customize the connect-api service
- [`connect-sync.env`](./connect-sync.env): an environment file used to customize the connect-sync service

## Overview

The open source Docker Engine tooling can be used to deploy 1Password Connect Server on any supported Linux distribution. A single-node swarm is sufficient for most deployments.

## Prerequisites

- AMD64/ARM64 VM or bare metal server with a Docker-supported Linux distribution (e.g. Ubuntu, Debian, Fedora, etc.)
- Docker Engine (see [Docker Engine installation overview](https://docs.docker.com/engine/install/#server)) installed on the Linux server
- a public DNS A record pointing to the Linux server
- SSH access to the Linux server
- [Docker Desktop](https://docs.docker.com/engine/install/#desktop) or Docker Engine installed on a machine with access to the Linux server and credentials from your 1Password account

## Get started

> [!Note]
> Before proceeding, review the [Get Started](https://developer.1password.com/docs/connect/get-started/) page from our 1Password Developer page.

Create a public DNS record that points to the public IP address of the Linux server for your Connect server if you wish to have this publicly available. For example, `op-connect-server.example.com`.

### 🛠️ Prepare the Linux server

On the Linux machine that you will be using as the Docker host for your Connect server:

1. If you haven't already done so, [install Docker Engine](https://docs.docker.com/engine/install/#server) on the Linux server. Follow the Server instructions; Docker Desktop is not needed for the Linux server.
2. Follow the [post-install steps](https://docs.docker.com/engine/install/linux-postinstall/) as noted in the documentation to enable running Docker as a non-root user and ensure that Docker Engine starts when the Linux server boots.

### 👨‍💻 Prepare your desktop

All following steps should be run on the same computer where you are already using 1Password, or another machine that can access the Linux server using SSH and has access to the `1password-credentials.json` file from the integration setup:

1. If you haven't already done so, install Docker. You can use [Docker Desktop](https://docs.docker.com/engine/install/#desktop), [install Docker Engine from binaries](https://docs.docker.com/engine/install/binaries/), or install Docker using your favorite package manager.

2. Open your preferred terminal. Clone this repository and switch to this directory:

   ```sh
   git clone https://github.com/1Password/connect.git
   cd ./connect/examples/beta/docker
   ```

3. Save the `1password-credentials.json` file from [the Connect Server setup](https://start.1password.com/developer-tools/infrastructure-secrets/connect/) to this working directory.

4. Open `connect-api.env` in your favorite text editor. Set the value of `OP_TLS_DOMAIN` to the fully qualified domain name of the public DNS record for your Connect server if you wish to have this publicly available, created in [Get started](#get-started). For example:

   ```dotenv
   # ...
   OP_TLS_DOMAIN=op-connect-server.example.com

   # ...
   ```

   Save the file.

5. Create a Docker context to use for connecting to the Linux server:

   _Example command:_

   ```sh
   docker context create op-connect \
       --description "1Password Connect Server Docker host" \
       --docker host=ssh://user@op-connect-server.example.com
   ```

   Copy the example command to a text editor. Replace `user` with the appropriate username for your Linux server and `op-connect-server.example.com` with the host name or IP address used for SSH access to the Linux server before running the command in your terminal.

   > **Note**
   >
   > 🔑 You can store your SSH keys in 1Password and authenticate this and other SSH workflows without writing a
   > private key to disk using the [1Password SSH agent](https://developer.1password.com/docs/ssh/get-started).

6. Switch to the Docker context you just created to connect to the Docker host:

   ```sh
   docker context use op-connect
   ```

### 🪄 Create a swarm

Run this command to create the swarm:

```sh
docker swarm init # --advertise-addr 192.0.2.1
```

> **Note**
>
> Additional nodes may be optionally added to a swarm for fault tolerance. This command adds the Linux server as the
> first (and only) node in the swarm, but Docker _requires_ a unique IP address to advertise to other nodes (even if
> no other nodes will be added). See [How nodes work](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/)
> in the Docker documentation for more details.
>
> If multiple IP addresses are detected, Docker returns an error; uncomment the `--advertise-addr` parameter (delete
> `#`), replace the example IP (`192.0.2.1`) with the appropriate IP address, and run the command again.

## Deploy 1Password Connect server

Use the Compose template to output a canonical configuration for use with Docker Swarm and create the stack from this configuration inline. Your Connect server should automatically acquire and manage a TLS certificate from Let's Encrypt on your behalf if configured this way:

```sh
docker stack config --compose-file ./compose.template.yaml \ --compose-file ./compose.lets-encrypt.yaml |
  docker stack deploy --compose-file - op-connect
```

## Test your Connect server

Run this command to view logs from the service for the Connect server container:

```sh
docker service logs op-connect_api --raw
```

Your **Connect server URL** is based on the fully qualified domain name of the DNS record created in [Get started](#get-started). For example: `https://op-connect.example.com`. Replace `mF_9.B5f-4.1JqM` with your bearer token and `https://op-connect.example.com` with your Connect server URL to test the connection and view status information.

```sh
curl --silent --show-error --request GET --header "Accept: application/json" \
  --header "Authorization: Bearer mF_9.B5f-4.1JqM" \
  https:/op-connect.example.com/health/v1/vaults
```

<details>
<summary>Example JSON response:</summary>

```json
[
  {
    "attributeVersion": 1,
    "contentVersion": 9,
    "createdAt": "2025-03-21T13:06:35Z",
    "id": "hgm3sn37vkj3lsdkeouq46wyca",
    "items": 5,
    "name": "REDACTED",
    "type": "USER_CREATED",
    "updatedAt": "2025-04-18T16:38:40Z"
  },
  {
    "attributeVersion": 1,
    "contentVersion": 2,
    "createdAt": "2025-03-21T02:15:33Z",
    "id": "mn4w65hq6ar7nc2fc7qgoru3ki",
    "items": 1,
    "name": "REDACTED",
    "type": "USER_CREATED",
    "updatedAt": "2025-03-21T02:16:32Z"
  },
  {
    "attributeVersion": 1,
    "contentVersion": 94,
    "createdAt": "2025-03-21T15:20:41Z",
    "id": "z4s56cbejab6q6urod3l5icqky",
    "items": 3,
    "name": "REDACTED",
    "type": "USER_CREATED",
    "updatedAt": "2025-04-29T02:49:13Z"
  }
]
```

</details>
<br />

## Update 1Password Connect server

Swarm mode in Docker Engine uses a declarative service model. Services will automatically restart tasks when updating their configuration.

Use the Docker context from [Prepare your desktop](#-prepare-your-desktop) to connect to your Docker host and manage your stack.

Update the `op-connect_api` and `op-connect_sync` services with the new image tags from the `1password/connect-api` and `1password/connect-sync`repository on Docker Hub to update your Connect server to a new version:

- https://hub.docker.com/r/1password/connect-api/tags
- https://hub.docker.com/r/1password/connect-sync/tags

```sh
docker service update op-connect_api --image 1password/connect-api:latest && \
   docker service update op-connect_sync --image 1password/connect-sync:latest

```

## Rotate credentials

Docker secrets are immutable and cannot be removed while in use by a Swarm service. To use new secret values in your stack, you must remove the existing secret from the service configuration, replace the Docker secret (or add a new one), and update the service configuration to mount the new secret value.

For example, if you create new credentials for the Connect server:

1. Scale down the `op-connect_api` and `op-connect_sync services to shut down the running Connect server.

   ```sh
   docker service scale op-connect_api=0 op-connect_sync=0
   ```

2. Update the service definition to unmount the the `1password-credentials.json` Docker secret:

   ```sh
   docker service update --secret-rm credentials op-connect_api && \
      docker service update --secret-rm credentials op-connect_sync
   ```

3. Remove the secret from the swarm:

   ```sh
   docker secret rm credentials
   ```

4. Copy the new `1password-credentials.json` file from your 1Password account to your working directory. Create a new `credentials` Docker secret using the new file:

   ```sh
   docker secret create credentials ./1password-credentials.json
   ```

5. Update the service to mount the new `1password-credentials.json` secret:

   ```sh
   docker service update --secret-add source=credentials,target=/home/opuser/.op/1password-credentials.json,uid="999",gid="999",mode=0440 op-connect_api && \
      docker service update --secret-add source=credentials,target=/home/opuser/.op/1password-credentials.json,uid="999",gid="999",mode=0440 op-connect_sync

   ```

6. Scale the `op-connect_api` and `op-connect_sync` services back up to reboot your Connect server:

   ```sh
   docker service scale op-connect_api=1 op-connect_sync=1
   ```

[Test your Connect server](#-test-your-Connect server) using the new bearer token associated with the new `1password-credentials.json` file.

A similar process can be used to update the values for any other Docker secrets used in your configuration.

## Appendix: Customize your Connect server

Many Connect server configuration changes can be made by adding or removing environment variables. These can be customized by making changes to [`connect-api.env`](./connect-api.env) and [`connect-sync.env`](./connect-sync.env) (that can be committed to your source control) before deploying (or redeploying) your Connect server using the `docker stack deploy` command. For some use cases, it may be desirable to update the configuration "on the fly" from your terminal.

For example, to reboot your Connect server with debug logging enabled:

```sh
docker service update op-connect_api \
    --env-add OP_DEBUG=1
```

To turn off debug logging and inject some color into the logs in your console:

```sh
docker service update op-connect_api \
    --env-rm OP_DEBUG \
    --env-add OP_PRETTY_LOGS=1
```

_Pretty logs pair nicely with the `--raw` parameter of the `docker service logs` command). 🤩_

### 🔒 Advanced TLS options

Identity providers strictly require an HTTPS endpoint with a valid TLS certificate to use for the Connect server URL. Your 1Password Connect server includes an optional CertificateManager component that (by default) acquires and manages a TLS certificate using Let's Encrypt, and terminates TLS traffic at the Connect server container using this certificate. This requires port 443 of the Docker host to be publicly accessible to ensure Let's Encrypt can initiate an inbound connection to your Connect server.

Other supported options include:

#### External load balancer or reverse proxy

To terminate TLS traffic at another public endpoint and redirect private traffic to a Docker host in your private network, a Connect server can be configured to disable the CertificateManager component and serve plain-text HTTP traffic on port 80 of the Docker host. CertificateManager will be enabled if a value is set for the `OP_TLS_DOMAIN` variable, so any value set in `connect-api.env` must be removed (or this line must be commented out). The included `compose.http.yaml` file can be used to set up the port mapping when deploying (or redeploying) a Connect server:

```sh
docker stack config \
    --compose-file ./compose.template.yaml \
    --compose-file ./compose.http.yaml |
        docker stack deploy --compose-file - op-connect
```

#### Self-managed TLS certificate

You may supply your own TLS certificate with CertificateManager instead of invoking Let's Encrypt. The value set for `OP_TLS_DOMAIN` must match the common name of the certificate.

Save the public and private certificate key files as `certificate.pem` and `key.pem` (respectively) to the working directory and use the included `compose.custom-tls.yaml` file when deploying your Connect server to create Docker secrets and configure a Connect server use this certificate when terminating TLS traffic:

```sh
docker stack config \
    --compose-file ./compose.template.yaml \
    --compose-file ./compose.custom-tls.yaml |
        docker stack deploy --compose-file - op-connect
```


================================================
FILE: examples/beta/docker/compose.custom-tls.yaml
================================================
services:
  api:
    environment: [OP_TLS_CERT_FILE=/home/opuser/.op/tls.crt, OP_TLS_KEY_FILE=/home/opuser/.op/tls.key]
    ports: [443:8443]
    secrets:
      - source: op-tls-crt
        target: /home/opuser/.op/tls.crt
        uid: "999"
        gid: "999"
        mode: 0440
      - source: op-tls-key
        target: /home/opuser/.op/tls.key
        uid: "999"
        gid: "999"
        mode: 0440
secrets:
  op-tls-crt:
    file: certificate.pem
    name: op-tls-crt
  op-tls-key:
    file: key.pem
    name: op-tls-key


================================================
FILE: examples/beta/docker/compose.http.yaml
================================================
services:
  api:
    ports: [80:8080]


================================================
FILE: examples/beta/docker/compose.lets-encrypt.yaml
================================================
services:
  api:
    environment:
      OP_TLS_USE_LETSENCRYPT: 1
    ports: [443:8443]


================================================
FILE: examples/beta/docker/compose.template.yaml
================================================
services:
  api:
    image: 1password/connect-api
    networks: [op-connect]
    deploy:
      replicas: 1
      restart_policy:
        condition: any
    secrets:
      - source: credentials
        target: /home/opuser/.op/1password-credentials.json
        uid: "999"
        gid: "999"
        mode: 0400
    volumes:
      - data:/home/opuser/.op/data
    env_file: connect-api.env
    user: "999:999"

  sync:
    image: 1password/connect-sync
    networks: [op-connect]
    deploy:
      replicas: 1
      restart_policy:
        condition: any
    secrets:
      - source: credentials
        target: /home/opuser/.op/1password-credentials.json
        uid: "999"
        gid: "999"
        mode: 0400
    volumes:
      - data:/home/opuser/.op/data
    env_file: connect-sync.env
    user: "999:999"

volumes:
  data:

secrets:
  credentials:
    file: 1password-credentials.json
    name: credentials

networks:
  op-connect:


================================================
FILE: examples/beta/docker/connect-api.env
================================================
# # To enable TLS termination using Let's Encrypt, set to the domain name of a public DNS record that points to the publicly available endpoint for your Connect server.
# OP_TLS_DOMAIN=connect.example.com

# # The logging level of the container.
# # Acceptable values: info, error, debug
# # Default value: info
# OP_LOG_LEVEL=debug

# The time (in seconds) to wait for the initial sync to complete.
# Acceptable values: A time duration (for example, 1h, 30m, 20s).
# Default value: 10s (10 seconds)
# OP_SYNC_TIMEOUT=1m

# # ---Manual bus configuration---

# # The port for listening to incoming bus connections from other containers.
# # Acceptable values: Any available port number.
# # Default value: A random free port
# OP_BUS_PORT=11223

# # A comma-separated list of [hostname]:[bus port] pairs of other containers to connect to.
# # Acceptable values: A comma-separated list of [hostname]:[bus port] pairs.
# # Default value: No default because this variable is optional.
# OP_BUS_PEERS=sync:11223


================================================
FILE: examples/beta/docker/connect-sync.env
================================================
# # The logging level of the container.
# # Acceptable values: info, error, debug
# # Default value: info
# OP_LOG_LEVEL=debug

# # ---Manual bus configuration---

# # The port for listening to incoming bus connections from other containers.
# # Acceptable values: Any available port number.
# # Default value: A random free port
# OP_BUS_PORT=11223

# # A comma-separated list of [hostname]:[bus port] pairs of other containers to connect to.
# # Acceptable values: A comma-separated list of [hostname]:[bus port] pairs.
# # Default value: No default because this variable is optional.
# OP_BUS_PEERS=api:11223


================================================
FILE: examples/docker/compose/README.md
================================================
# Docker Compose

The `docker-compose.yaml` file in this directory is a simple example of how to run 1Password Connect using Docker Compose.

## Getting Started

Before you begin, make sure you have your Connect server's `1password-credentials.json` file on your machine. If the credentials file is not located in the same directory as the `docker-compose.yaml` file, update the `volumes` section for both the Sync and API containers:

```yaml
volumes:
  - "<filepath_here>/1password-credentials.json:/home/opuser/.op/1password-credentials.json"
```

### Start the Docker containers:
```bash
docker-compose up [-d]
```

The Connect API will be accessible through port `8080` on the host machine:
```curl
curl \
-H "Accept: application/json" \
-H "Authorization: Bearer $OP_API_TOKEN" \
http://localhost:8080/v1/vaults
```

================================================
FILE: examples/docker/compose/docker-compose.yaml
================================================
version: "3.4"

services:
  op-connect-api:
    image: 1password/connect-api:latest
    ports:
      - "8080:8080"
    volumes:
      - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json"
      - "data:/home/opuser/.op/data"
  op-connect-sync:
    image: 1password/connect-sync:latest
    ports:
      - "8081:8080"
    volumes:
      - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json"
      - "data:/home/opuser/.op/data"

volumes:
  data:


================================================
FILE: examples/kubernetes/README.md
================================================
# Kubernetes Deployment

This is an example Kubernetes deployment for 1Password Connect that uses [cert-manager](https://cert-manager.io) to provision a TLS certificate for your external domain.

Included in this folder are:

- [`op-connect-deployment.yaml`](op-connect-deployment.yaml): Deployment resource to create the Pod for 1Password Connect
  - `connect-sync` container to sync vault(s) from 1Password.com
  - `connect-api` container to serve 1Password Connect API
  - Shared ephemeral volume for data
- [`op-connect-service.yaml`](op-connect-service.yaml): Service resource for connecting to the above Pod
- [`op-connect-issuer.yaml`](op-connect-issuer.yaml): cert-manager ClusterIssuer resource to manage TLS certificates using [Let's Encrypt](https://letsencrypt.org/)
- [`op-connect-ingress.yaml`](op-connect-ingress.yaml): Ingress resource to route TLS requests to 1Password Connect

## Prerequisites

- 1Password Connect server credentials file (see [Quick Start](/README.md#quick-start) in the root of this repository)
- Kubernetes cluster with available [ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/), e.g.:
  - [Ingress NGINX Controller](https://kubernetes.github.io/ingress-nginx/deploy/)
  - [GKE Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress)
  - [AWS Load Balancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/installation/)
  - [Azure Application Gateway Ingress Controller](https://learn.microsoft.com/en-us/azure/application-gateway/tutorial-ingress-controller-add-on-new)
- [cert-manager](https://cert-manager.io) installed on your cluster (see [Installation - cert-manager Documentation](https://cert-manager.io/docs/installation/))
- A DNS provider in which you can create public records.

## Getting started

1. If you haven't already done so, save your `1password-credentials.json` file locally.

2. Create a Kubernetes Secret for the credentials file:

    ```sh
    kubectl create secret generic op-credentials --from-file=1password-credentials.json=./1password-credentials.json
    ```

    > **Note**
    >
    > If saved with a different filename or somewhere other than the working
    > directory, replace `./1password-credentials.json` with the path to your
    > credentials file, i.e.:
    >
    > ```sh
    > kubectl create secret generic op-credentials --from-file=1password-credentials.json=path/to/credentials-file.json
    > ```

3. Clone this repository and switch to this directory:

    ```sh
    git clone https://github.com/1Password/connect.git
    cd ./connect/examples/kubernetes
    ```

4. Edit [`op-connect-issuer.yaml`](op-connect-issuer.yaml). Replace the value for `spec.acme.email` with a valid email address:

    ```yaml
    ...
    spec:
      acme:
        ...
        email: user@example.com
    ...
    ```

    Save the file.

5. Edit [`op-connect-ingress.yaml`](op-connect-ingress.yaml). Replace `connect.example.com` with the fully-qualified domain name you will use for 1Password Connect (use the same FQDN for both fields):

    ```yaml
    ...
    spec: 
      rules:
        - host: connect.example.com
    ...
    tls:
      - hosts:
        - connect.example.com
    ...
    ```

    > **Warning**
    >
    > Depending on your chosen ingress controller, you may also need to add
    > additional annotations or make other changes to this manifest.

    Save the file.

## Deploy 1Password Connect

1. Apply the manifests to your cluster:

    ```sh
    kubectl apply -f .
    ```

    > **Note**
    >
    > The `.` in this command applies all `.yaml` and `.json` files in the
    > working directory. If this includes the 1Password Connect credentials
    > file, the output returned  by `kubectl` may include something similar to:
    >
    >>```output
    >> error: error validating "1password-credentials.json":...
    >> ```
    >
    > This error can safely be ignored.

2. Get the IP address of the ingress controller:

    ```sh
    kubectl get ing/connect-ingress
    ```

    The IP address for this Ingress resource will be listed under the `ADDRESS` column.

    > **Note**
    >
    > Depending on your cluster or cloud provider, it may be several minutes
    > before an IP address is assigned. If there is no IP address listed yet,
    > run the same command again in a few minutes.

3. Create a DNS record that points to the above address. cert-manager will automatically acquire a TLS certificate on your behalf (this may take a few minutes).

## Test the connection

Confirm public access by connecting using HTTPS from outside your cluster:

> **Note**
>
> You will need to set the value of the `$OP_API_TOKEN` variable to an active token for this Connect environment.

```sh
curl \
  -X GET \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $OP_API_TOKEN" \
  https://connect.example.com/v1/vaults
```

This will also intialize the connection to 1Password.com and complete the first valut data sync.

## Appendix

### Updating 1Password Connect

When a new version of 1Password Connect is released, set the image version for each of the containers to rollout a new revision:

```sh
OP_CONNECT_VERSION=1.5.7; kubectl set image deploy/op-connect \
  connect-api=1password/connect-api:$OP_CONNECT_VERSION \
  connect-sync=1password/connect-sync:$OP_CONNECT_VERSION
```


================================================
FILE: examples/kubernetes/op-connect-deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: op-connect
spec:
  selector:
    matchLabels:
      app: op-connect
  template:
    metadata:
      labels:
        app: op-connect
    spec:
      volumes:
        - name: shared-data
          emptyDir: {}
        - name: credentials
          secret:
            secretName: op-credentials
      initContainers:
        - name: sqlite-permissions
          image: alpine:3.12
          command:
            - "/bin/sh"
            - "-c"
          args:
            - "mkdir -p /home/opuser/.op/data && chown -R 999 /home/opuser && chmod -R 700 /home/opuser && chmod -f -R 600 /home/opuser/.op/config || :"
          volumeMounts:
            - mountPath: /home/opuser/.op/data
              name: shared-data
      containers:
        - name: connect-api
          image: 1password/connect-api:latest
          resources:
            limits:
              memory: "128Mi"
              cpu: "0.2"
          ports:
            - name: connect-api
              containerPort: 8080
          env:
            - name: OP_SESSION
              value: /home/opuser/.config/1password-credentials.json
          volumeMounts:
            - mountPath: /home/opuser/.op/data
              name: shared-data
            - mountPath: /home/opuser/.config
              name: credentials
              readOnly: true
          livenessProbe:
            httpGet:
              path: /heartbeat
              port: connect-api
            initialDelaySeconds: 3
            periodSeconds: 3
          readinessProbe:
            httpGet:
              path: /health
              port: connect-api
            initialDelaySeconds: 5
            periodSeconds: 5
        - name: connect-sync
          image: 1password/connect-sync:latest
          resources:
            limits:
              memory: "128Mi"
              cpu: "0.2"
          ports:
            - name: connect-sync
              containerPort: 8081
          env:
            - name: OP_SESSION
              value: /home/opuser/.config/1password-credentials.json
            - name: OP_HTTP_PORT
              value: "8081"
          volumeMounts:
            - mountPath: /home/opuser/.op/data
              name: shared-data
            - mountPath: /home/opuser/.config
              name: credentials
              readOnly: true
          livenessProbe:
            httpGet:
              path: /heartbeat
              port: connect-sync
            initialDelaySeconds: 3
            periodSeconds: 3
          readinessProbe:
            httpGet:
              path: /health
              port: connect-sync
            initialDelaySeconds: 5
            periodSeconds: 5


================================================
FILE: examples/kubernetes/op-connect-ingress.yaml
================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: connect-ingress
  annotations:
    # Use cert-manager ClusterIssuer
    cert-manager.io/cluster-issuer: letsencrypt
    # Allow cert-manager to edit this Ingress resource in-place
    acme.cert-manager.io/http01-edit-in-place: "true"
    # Issue temporary self-signed cert
    cert-manager.io/issue-temporary-certificate: "true"
spec:
  # You may need to specify the IngressClass if your cluster does not have a
  # default IngressClass, e.g. for Ingress NGINX Controller, uncomment the
  # below line:
  # ingressClassName: nginx
  rules:
    - host: connect.example.com
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: op-connect
              port:
                name: connect-api
  tls:
    - hosts:
      - connect.example.com
      secretName: connect-cert


================================================
FILE: examples/kubernetes/op-connect-issuer.yaml
================================================
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    # You *must* replace this value with a valid email address.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: user@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource that will be used to store the account's private key.
      name: letsencrypt-issuer-account-key
    solvers:
    # Use HTTP01 solver
    - http01:
        ingress:
          # Edit existing Ingress
          name: connect-ingress


================================================
FILE: examples/kubernetes/op-connect-service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
  name: op-connect
spec:
  selector:
    app: op-connect
  ports:
    - name: connect-api
      port: 8080
      targetPort: connect-api
 
Download .txt
gitextract__1cd96aw/

├── CHANGELOG.md
├── README.md
├── docs/
│   ├── configuration.md
│   └── openapi/
│       └── spec.yaml
└── examples/
    ├── aws-ecs-fargate/
    │   ├── README.md
    │   └── connect-server.yaml
    ├── beta/
    │   ├── aws-ecs-apigateway-cfn/
    │   │   ├── README.md
    │   │   └── connect-server.yaml
    │   ├── azure-container-apps-arm/
    │   │   ├── README.md
    │   │   └── aca-op-connect-server-template.json
    │   └── docker/
    │       ├── README.md
    │       ├── compose.custom-tls.yaml
    │       ├── compose.http.yaml
    │       ├── compose.lets-encrypt.yaml
    │       ├── compose.template.yaml
    │       ├── connect-api.env
    │       └── connect-sync.env
    ├── docker/
    │   └── compose/
    │       ├── README.md
    │       └── docker-compose.yaml
    └── kubernetes/
        ├── README.md
        ├── op-connect-deployment.yaml
        ├── op-connect-ingress.yaml
        ├── op-connect-issuer.yaml
        └── op-connect-service.yaml
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (131K chars).
[
  {
    "path": "CHANGELOG.md",
    "chars": 10445,
    "preview": "[//]: # \"START/v1.8.0\"\n\n# v1.8.0\nThis release includes improvements for fetching files with Connect, like support for fe"
  },
  {
    "path": "README.md",
    "chars": 1160,
    "preview": "<img src=\"https://github.com/1Password/connect/assets/46452606/0f7cf2a8-a290-41fc-b78d-3dfb1017f9be\" alt=\"\" role=\"img\" >"
  },
  {
    "path": "docs/configuration.md",
    "chars": 2949,
    "preview": "# Connect Server Configuration\n\nThe Connect server consists of 2 containers, `1password/connect-api` and `1password/conn"
  },
  {
    "path": "docs/openapi/spec.yaml",
    "chars": 38164,
    "preview": "openapi: \"3.0.2\"\ninfo:\n  title: 1Password Connect\n  description: >-\n    REST API interface for 1Password Connect.\n  vers"
  },
  {
    "path": "examples/aws-ecs-fargate/README.md",
    "chars": 2741,
    "preview": "# AWS ECS with Fargate\n\nThe CloudFormation document in this folder will create the following resources, along with the n"
  },
  {
    "path": "examples/aws-ecs-fargate/connect-server.yaml",
    "chars": 14603,
    "preview": "AWSTemplateFormatVersion: 2010-09-09\nDescription: >-\n  Sets up 1Password Connect on Fargate with an ALB for HTTPS ingres"
  },
  {
    "path": "examples/beta/aws-ecs-apigateway-cfn/README.md",
    "chars": 1798,
    "preview": "# AWS ECS with Fargate and API Gateway\n\nThe CloudFormation document in this folder will create the following resources, "
  },
  {
    "path": "examples/beta/aws-ecs-apigateway-cfn/connect-server.yaml",
    "chars": 12216,
    "preview": "AWSTemplateFormatVersion: 2010-09-09\nDescription: Sets up 1Password Connect on Fargate with API Gateway, stores 1passwor"
  },
  {
    "path": "examples/beta/azure-container-apps-arm/README.md",
    "chars": 6766,
    "preview": "# Deploy 1Password Connect Server on Azure Container Apps using the Azure Portal\n\n_Learn how to deploy 1Password Connect"
  },
  {
    "path": "examples/beta/azure-container-apps-arm/aca-op-connect-server-template.json",
    "chars": 5080,
    "preview": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#\",\n  \"contentVersion\": \"1"
  },
  {
    "path": "examples/beta/docker/README.md",
    "chars": 14224,
    "preview": "# [Beta] Deploy 1Password Connect Server using Docker Swarm\n\nThis example describes how to deploy 1Password Connect Serv"
  },
  {
    "path": "examples/beta/docker/compose.custom-tls.yaml",
    "chars": 528,
    "preview": "services:\n  api:\n    environment: [OP_TLS_CERT_FILE=/home/opuser/.op/tls.crt, OP_TLS_KEY_FILE=/home/opuser/.op/tls.key]\n"
  },
  {
    "path": "examples/beta/docker/compose.http.yaml",
    "chars": 38,
    "preview": "services:\n  api:\n    ports: [80:8080]\n"
  },
  {
    "path": "examples/beta/docker/compose.lets-encrypt.yaml",
    "chars": 88,
    "preview": "services:\n  api:\n    environment:\n      OP_TLS_USE_LETSENCRYPT: 1\n    ports: [443:8443]\n"
  },
  {
    "path": "examples/beta/docker/compose.template.yaml",
    "chars": 937,
    "preview": "services:\n  api:\n    image: 1password/connect-api\n    networks: [op-connect]\n    deploy:\n      replicas: 1\n      restart"
  },
  {
    "path": "examples/beta/docker/connect-api.env",
    "chars": 1007,
    "preview": "# # To enable TLS termination using Let's Encrypt, set to the domain name of a public DNS record that points to the publ"
  },
  {
    "path": "examples/beta/docker/connect-sync.env",
    "chars": 612,
    "preview": "# # The logging level of the container.\n# # Acceptable values: info, error, debug\n# # Default value: info\n# OP_LOG_LEVEL"
  },
  {
    "path": "examples/docker/compose/README.md",
    "chars": 821,
    "preview": "# Docker Compose\n\nThe `docker-compose.yaml` file in this directory is a simple example of how to run 1Password Connect u"
  },
  {
    "path": "examples/docker/compose/docker-compose.yaml",
    "chars": 490,
    "preview": "version: \"3.4\"\n\nservices:\n  op-connect-api:\n    image: 1password/connect-api:latest\n    ports:\n      - \"8080:8080\"\n    v"
  },
  {
    "path": "examples/kubernetes/README.md",
    "chars": 5396,
    "preview": "# Kubernetes Deployment\n\nThis is an example Kubernetes deployment for 1Password Connect that uses [cert-manager](https:/"
  },
  {
    "path": "examples/kubernetes/op-connect-deployment.yaml",
    "chars": 2696,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: op-connect\nspec:\n  selector:\n    matchLabels:\n      app: op-conne"
  },
  {
    "path": "examples/kubernetes/op-connect-ingress.yaml",
    "chars": 913,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: connect-ingress\n  annotations:\n    # Use cert-manager C"
  },
  {
    "path": "examples/kubernetes/op-connect-issuer.yaml",
    "chars": 646,
    "preview": "apiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt\nspec:\n  acme:\n    # You *must* replace "
  },
  {
    "path": "examples/kubernetes/op-connect-service.yaml",
    "chars": 177,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: op-connect\nspec:\n  selector:\n    app: op-connect\n  ports:\n    - name: con"
  }
]

About this extraction

This page contains the full source code of the 1Password/connect GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (121.6 KB), approximately 29.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!