Introduce collectionid filter for bitwarden parameters (#454)

This commit is contained in:
Kerem Yilmaz 2024-06-10 22:06:58 -07:00 committed by GitHub
parent bb19a8ab8e
commit 0ede4fdfa0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 56 additions and 10 deletions

View file

@ -0,0 +1,33 @@
"""Add collection id to bitwarden credential parameters
Revision ID: 2c163e606a3d
Revises: 312d305c6b18
Create Date: 2024-06-11 05:02:25.023252+00:00
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "2c163e606a3d"
down_revision: Union[str, None] = "312d305c6b18"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"bitwarden_login_credential_parameters", sa.Column("bitwarden_collection_id", sa.String(), nullable=True)
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("bitwarden_login_credential_parameters", "bitwarden_collection_id")
# ### end Alembic commands ###

View file

@ -1026,6 +1026,7 @@ class AgentDB:
url_parameter_key: str, url_parameter_key: str,
key: str, key: str,
description: str | None = None, description: str | None = None,
bitwarden_collection_id: str | None = None,
) -> BitwardenLoginCredentialParameter: ) -> BitwardenLoginCredentialParameter:
async with self.Session() as session: async with self.Session() as session:
bitwarden_login_credential_parameter = BitwardenLoginCredentialParameterModel( bitwarden_login_credential_parameter = BitwardenLoginCredentialParameterModel(
@ -1036,6 +1037,7 @@ class AgentDB:
url_parameter_key=url_parameter_key, url_parameter_key=url_parameter_key,
key=key, key=key,
description=description, description=description,
bitwarden_collection_id=bitwarden_collection_id,
) )
session.add(bitwarden_login_credential_parameter) session.add(bitwarden_login_credential_parameter)
await session.commit() await session.commit()

View file

@ -278,6 +278,7 @@ class BitwardenLoginCredentialParameterModel(Base):
bitwarden_client_id_aws_secret_key = Column(String, nullable=False) bitwarden_client_id_aws_secret_key = Column(String, nullable=False)
bitwarden_client_secret_aws_secret_key = Column(String, nullable=False) bitwarden_client_secret_aws_secret_key = Column(String, nullable=False)
bitwarden_master_password_aws_secret_key = Column(String, nullable=False) bitwarden_master_password_aws_secret_key = Column(String, nullable=False)
bitwarden_collection_id = Column(String, nullable=True, default=None)
url_parameter_key = Column(String, nullable=False) url_parameter_key = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
modified_at = Column( modified_at = Column(

View file

@ -241,6 +241,7 @@ def convert_to_bitwarden_login_credential_parameter(
LOG.debug( LOG.debug(
"Converting BitwardenLoginCredentialParameterModel to BitwardenLoginCredentialParameter", "Converting BitwardenLoginCredentialParameterModel to BitwardenLoginCredentialParameter",
bitwarden_login_credential_parameter_id=bitwarden_login_credential_parameter_model.bitwarden_login_credential_parameter_id, bitwarden_login_credential_parameter_id=bitwarden_login_credential_parameter_model.bitwarden_login_credential_parameter_id,
bitwarden_collection_id=bitwarden_login_credential_parameter_model.bitwarden_collection_id,
) )
return BitwardenLoginCredentialParameter( return BitwardenLoginCredentialParameter(
@ -251,6 +252,7 @@ def convert_to_bitwarden_login_credential_parameter(
bitwarden_client_id_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_client_id_aws_secret_key, bitwarden_client_id_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_client_secret_aws_secret_key, bitwarden_client_secret_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_master_password_aws_secret_key, bitwarden_master_password_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_master_password_aws_secret_key,
bitwarden_collection_id=bitwarden_login_credential_parameter_model.bitwarden_collection_id,
url_parameter_key=bitwarden_login_credential_parameter_model.url_parameter_key, url_parameter_key=bitwarden_login_credential_parameter_model.url_parameter_key,
created_at=bitwarden_login_credential_parameter_model.created_at, created_at=bitwarden_login_credential_parameter_model.created_at,
modified_at=bitwarden_login_credential_parameter_model.modified_at, modified_at=bitwarden_login_credential_parameter_model.modified_at,

View file

@ -27,6 +27,7 @@ class BitwardenConstants(StrEnum):
CLIENT_SECRET = "BW_CLIENT_SECRET" CLIENT_SECRET = "BW_CLIENT_SECRET"
MASTER_PASSWORD = "BW_MASTER_PASSWORD" MASTER_PASSWORD = "BW_MASTER_PASSWORD"
URL = "BW_URL" URL = "BW_URL"
BW_COLLECTION_ID = "BW_COLLECTION_ID"
USERNAME = "BW_USERNAME" USERNAME = "BW_USERNAME"
PASSWORD = "BW_PASSWORD" PASSWORD = "BW_PASSWORD"
@ -68,6 +69,7 @@ class BitwardenService:
client_secret: str, client_secret: str,
master_password: str, master_password: str,
url: str, url: str,
collection_id: str | None = None,
) -> dict[str, str]: ) -> dict[str, str]:
""" """
Get the secret value from the Bitwarden CLI. Get the secret value from the Bitwarden CLI.
@ -105,14 +107,6 @@ class BitwardenService:
f"Failed to unlock vault. stdout: {unlock_result.stdout} stderr: {unlock_result.stderr}" f"Failed to unlock vault. stdout: {unlock_result.stdout} stderr: {unlock_result.stderr}"
) )
# This is a part of Bitwarden's client-side telemetry
# TODO -- figure out how to disable this telemetry so we never get this error
# https://github.com/bitwarden/clients/blob/9d10825dbd891c0f41fe1b4c4dd3ca4171f63be5/libs/common/src/services/api.service.ts#L1473
if unlock_result.stderr and "Event post failed" not in unlock_result.stderr:
raise BitwardenUnlockError(
f"Failed to unlock vault. stdout: {unlock_result.stdout} stderr: {unlock_result.stderr}"
)
# Extract session key # Extract session key
try: try:
session_key = BitwardenService._extract_session_key(unlock_result.stdout) session_key = BitwardenService._extract_session_key(unlock_result.stdout)
@ -132,6 +126,9 @@ class BitwardenService:
"--session", "--session",
session_key, session_key,
] ]
if collection_id:
LOG.info("Collection ID is provided, filtering items by collection ID", collection_id=collection_id)
list_command.extend(["--collectionid", collection_id])
items_result = BitwardenService.run_command(list_command) items_result = BitwardenService.run_command(list_command)
if items_result.stderr and "Event post failed" not in items_result.stderr: if items_result.stderr and "Event post failed" not in items_result.stderr:
@ -144,7 +141,8 @@ class BitwardenService:
raise BitwardenListItemsError("Failed to parse items JSON. Output: " + items_result.stdout) raise BitwardenListItemsError("Failed to parse items JSON. Output: " + items_result.stdout)
if not items: if not items:
raise BitwardenListItemsError("No items found in Bitwarden.") collection_id_str = f" in collection with ID: {collection_id}" if collection_id else ""
raise BitwardenListItemsError(f"No items found in Bitwarden for URL: {url}{collection_id_str}")
totp_command = ["bw", "get", "totp", url, "--session", session_key] totp_command = ["bw", "get", "totp", url, "--session", session_key]
totp_result = BitwardenService.run_command(totp_command) totp_result = BitwardenService.run_command(totp_command)

View file

@ -157,12 +157,14 @@ class WorkflowRunContext:
client_secret, client_secret,
master_password, master_password,
url, url,
collection_id=parameter.bitwarden_collection_id,
) )
if secret_credentials: if secret_credentials:
self.secrets[BitwardenConstants.URL] = url self.secrets[BitwardenConstants.URL] = url
self.secrets[BitwardenConstants.CLIENT_SECRET] = client_secret self.secrets[BitwardenConstants.CLIENT_SECRET] = client_secret
self.secrets[BitwardenConstants.CLIENT_ID] = client_id self.secrets[BitwardenConstants.CLIENT_ID] = client_id
self.secrets[BitwardenConstants.MASTER_PASSWORD] = master_password self.secrets[BitwardenConstants.MASTER_PASSWORD] = master_password
self.secrets[BitwardenConstants.BW_COLLECTION_ID] = parameter.bitwarden_collection_id
random_secret_id = self.generate_random_secret_id() random_secret_id = self.generate_random_secret_id()
# username secret # username secret
@ -181,7 +183,6 @@ class WorkflowRunContext:
"totp": totp_secret_id, "totp": totp_secret_id,
} }
except BitwardenBaseError as e: except BitwardenBaseError as e:
BitwardenService.logout()
LOG.error(f"Failed to get secret from Bitwarden. Error: {e}") LOG.error(f"Failed to get secret from Bitwarden. Error: {e}")
raise e raise e
elif isinstance(parameter, ContextParameter): elif isinstance(parameter, ContextParameter):

View file

@ -52,6 +52,9 @@ class BitwardenLoginCredentialParameter(Parameter):
bitwarden_master_password_aws_secret_key: str bitwarden_master_password_aws_secret_key: str
# url to request the login credentials from bitwarden # url to request the login credentials from bitwarden
url_parameter_key: str url_parameter_key: str
# bitwarden collection id to filter the login credentials from,
# if not provided, no filtering will be done
bitwarden_collection_id: str | None = None
created_at: datetime created_at: datetime
modified_at: datetime modified_at: datetime

View file

@ -36,6 +36,9 @@ class BitwardenLoginCredentialParameterYAML(ParameterYAML):
bitwarden_master_password_aws_secret_key: str bitwarden_master_password_aws_secret_key: str
# parameter key for the url to request the login credentials from bitwarden # parameter key for the url to request the login credentials from bitwarden
url_parameter_key: str url_parameter_key: str
# bitwarden collection id to filter the login credentials from,
# if not provided, no filtering will be done
bitwarden_collection_id: str | None = None
class WorkflowParameterYAML(ParameterYAML): class WorkflowParameterYAML(ParameterYAML):

View file

@ -450,6 +450,7 @@ class WorkflowService:
url_parameter_key: str, url_parameter_key: str,
key: str, key: str,
description: str | None = None, description: str | None = None,
bitwarden_collection_id: str | None = None,
) -> Parameter: ) -> Parameter:
return await app.DATABASE.create_bitwarden_login_credential_parameter( return await app.DATABASE.create_bitwarden_login_credential_parameter(
workflow_id=workflow_id, workflow_id=workflow_id,
@ -459,6 +460,7 @@ class WorkflowService:
url_parameter_key=url_parameter_key, url_parameter_key=url_parameter_key,
key=key, key=key,
description=description, description=description,
bitwarden_collection_id=bitwarden_collection_id,
) )
async def create_output_parameter( async def create_output_parameter(
@ -833,6 +835,7 @@ class WorkflowService:
url_parameter_key=parameter.url_parameter_key, url_parameter_key=parameter.url_parameter_key,
key=parameter.key, key=parameter.key,
description=parameter.description, description=parameter.description,
bitwarden_collection_id=parameter.bitwarden_collection_id,
) )
elif parameter.parameter_type == ParameterType.WORKFLOW: elif parameter.parameter_type == ParameterType.WORKFLOW:
parameters[parameter.key] = await self.create_workflow_parameter( parameters[parameter.key] = await self.create_workflow_parameter(