-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
860 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,4 @@ They are not considered stable and may experience breaking changes in the future | |
:maxdepth: 1 | ||
|
||
kubectl | ||
op |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
.. caution:: | ||
|
||
This provider is still in the experimental stage and may change in the future. | ||
|
||
1Password CLI provider | ||
====================== | ||
|
||
Read values from 1Password using the `op`_ command. | ||
|
||
.. _op: https://developer.1password.com/docs/cli | ||
|
||
Source type | ||
``1password:op`` | ||
|
||
.. important:: | ||
|
||
To utilize this provider, verify that the ``op`` command is installed and properly configured. | ||
|
||
Importantly, you need to enable the *Integrate with 1Password CLI* option in the 1Password Desktop app. | ||
For additional information, refer to the `official installation guide`_. | ||
|
||
.. _official installation guide: https://developer.1password.com/docs/cli/get-started | ||
|
||
Configuration layout | ||
-------------------- | ||
|
||
.. tab-set:: | ||
|
||
.. tab-item:: toml | ||
:sync: toml | ||
|
||
.. code-block:: toml | ||
[[sources]] | ||
type = "1password:op" | ||
name = "op" | ||
[[secrets]] | ||
name = "WP_USER" | ||
source = "op" | ||
ref = "Wordpress" | ||
field = "password" | ||
.. tab-item:: yaml | ||
:sync: yaml | ||
|
||
.. code-block:: yaml | ||
sources: | ||
- type: 1password:op | ||
name: op | ||
secrets: | ||
- name: WP_USER | ||
source: op | ||
ref: Wordpress | ||
field: password | ||
.. tab-item:: json | ||
|
||
.. code-block:: json | ||
{ | ||
"sources": [ | ||
{ | ||
"type": "1password:op", | ||
"name": "op" | ||
} | ||
], | ||
"secrets": [ | ||
{ | ||
"name": "WP_USER", | ||
"source": "op", | ||
"ref": "Wordpress", | ||
"field": "password" | ||
} | ||
] | ||
} | ||
.. tab-item:: pyproject.toml | ||
|
||
.. code-block:: toml | ||
[[tool.secrets-env.sources]] | ||
type = "1password:op" | ||
name = "op" | ||
[[tool.secrets-env.secrets]] | ||
name = "WP_USER" | ||
source = "op" | ||
ref = "Wordpress" | ||
field = "password" | ||
Source section | ||
-------------- | ||
|
||
.. tip:: | ||
|
||
All source configuration are optional. | ||
|
||
``op-path`` | ||
^^^^^^^^^^^ | ||
|
||
Defines the path to the ``op`` command. By default, the system path is used. | ||
|
||
Secrets section | ||
--------------- | ||
|
||
The configuration in the ``secrets`` section defines the item and field to retrieve from 1Password. | ||
|
||
.. note:: | ||
|
||
A field name followed by a bookmark icon (:octicon:`bookmark`) indicates that it is a required parameter. | ||
|
||
``ref`` :octicon:`bookmark` | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
The item ID or name in 1Password. | ||
|
||
The value can be either the item's UUID or its title, and it is case-insensitive. | ||
|
||
``field`` :octicon:`bookmark` | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
Field to retrieve from the item. | ||
|
||
Both field names and field UUIDs are supported, and they are case-insensitive. | ||
|
||
|
||
Simplified layout | ||
----------------- | ||
|
||
This provider accepts 1Password's `secret reference`_ as the simplified representation. | ||
|
||
.. _secret reference: https://developer.1password.com/docs/cli/secret-reference-syntax/ | ||
|
||
.. tab-set:: | ||
|
||
.. tab-item:: toml :bdg:`simplified` | ||
:sync: toml | ||
|
||
.. code-block:: toml | ||
[sources] | ||
type = "1password:op" | ||
[secrets] | ||
WP_USER = "op://Private/2yysndf2j5bhracufqakofhb3e/email" | ||
.. tab-item:: yaml :bdg:`simplified` | ||
:sync: yaml | ||
|
||
.. code-block:: yaml | ||
sources: | ||
- type: 1password:op | ||
secrets: | ||
WP_USER: "op://Private/2yysndf2j5bhracufqakofhb3e/email" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
from __future__ import annotations | ||
|
||
import datetime # noqa: TCH003 | ||
from typing import Annotated, Literal | ||
|
||
from pydantic import ( | ||
AnyUrl, | ||
BaseModel, | ||
ConfigDict, | ||
Field, | ||
SecretStr, | ||
UrlConstraints, | ||
ValidationError, | ||
model_validator, | ||
validate_call, | ||
) | ||
|
||
SecretReference = Annotated[ | ||
AnyUrl, | ||
UrlConstraints(allowed_schemes=["op"]), | ||
] | ||
|
||
|
||
class OpRequest(BaseModel): | ||
""" | ||
Spec for requesting a value from 1Password. | ||
""" | ||
|
||
ref: str | ||
field: str | ||
|
||
@model_validator(mode="before") | ||
@classmethod | ||
def _accept_secret_ref(cls, values): | ||
if isinstance(values, dict): | ||
# shortcut: accept secret reference as a single value | ||
if shortcut := values.get("value"): | ||
return parse_secret_reference(shortcut) | ||
|
||
# attempt: accept secret reference in `ref` field | ||
if ref := values.get("ref"): | ||
try: | ||
return parse_secret_reference(ref) | ||
except ValidationError: | ||
pass | ||
|
||
return values | ||
|
||
|
||
@validate_call | ||
def parse_secret_reference(u: SecretReference) -> dict[str, str]: | ||
""" | ||
Parse a secret reference string. | ||
Ref: | ||
https://developer.1password.com/docs/cli/secret-reference-syntax/ | ||
""" | ||
path = u.path or "/" | ||
parts = path.split("/") | ||
|
||
if len(parts) == 3: | ||
_, item, field = parts | ||
elif len(parts) == 4: | ||
_, item, section, field = parts | ||
else: | ||
raise ValueError("URL path should be in the format of '/item/section/field'") | ||
|
||
return { | ||
"ref": item, | ||
"field": field, | ||
} | ||
|
||
|
||
class ItemObject(BaseModel): | ||
""" | ||
Item in the 1Password Vault. | ||
Ref: | ||
https://developer.1password.com/docs/connect/connect-api-reference/#item-object | ||
""" | ||
|
||
# NOTE | ||
# Response from API and command line tool has different casing. | ||
# This is a workaround to handle both cases. | ||
model_config = ConfigDict(populate_by_name=True) | ||
|
||
id: str | ||
category: str | ||
created_at: datetime.datetime = Field(alias="createdAt") | ||
fields: list[FieldObject] = Field(default_factory=list) | ||
tags: list[str] = Field(default_factory=list) | ||
title: str | ||
updated_at: datetime.datetime = Field(alias="updatedAt") | ||
|
||
def get_field(self, name: str) -> FieldObject: | ||
""" | ||
Get a field by ID or name. | ||
""" | ||
iname = name.lower() | ||
|
||
def match_attr(attr_name: str): | ||
for field in self.fields: | ||
attr = getattr(field, attr_name, None) | ||
if not attr: | ||
continue | ||
if attr.lower() == iname: | ||
return field | ||
|
||
if field := match_attr("id"): | ||
return field | ||
if field := match_attr("label"): | ||
return field | ||
|
||
raise LookupError(f'Item {self.title} ({self.id}) has no field "{name}"') | ||
|
||
|
||
class FieldObject(BaseModel): | ||
""" | ||
Field in the 1Password item object. | ||
Ref: | ||
https://developer.1password.com/docs/connect/connect-api-reference/#item-field-object | ||
""" | ||
|
||
id: str | ||
type: str | ||
purpose: Literal["USERNAME", "PASSWORD", "NOTES"] | None = None | ||
label: str | None = None | ||
value: SecretStr | None = None |
Oops, something went wrong.