Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port Azure ID token to v3 #208

Merged
merged 5 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- "T4627_py3_main"
- "T5281_port_cc_token"
- "T5285_port_azure_token"

jobs:
tests:
Expand Down Expand Up @@ -111,7 +111,7 @@ jobs:
export CANARY_MAILGUN_API_KEY=${{ secrets.TESTING_MAILGUN_API_KEY }}
export CANARY_SENTRY_ENVIRONMENT=ci
cd tests
poetry run coverage run --source=../canarytokens --omit="integration/test_custom_binary.py,integration/test_sql_server_token.py" -m pytest units --runv3
poetry run coverage run --source=../canarytokens --omit="integration/test_custom_binary.py,integration/test_sql_server_token.py" -m pytest units --runv3 -v

- name: Check coverage is over threshold percentage for unit tests
# Here we check coverage info on all tests
Expand All @@ -128,13 +128,13 @@ jobs:
sleep 10
export TEST_NETWORK=`docker network ls | grep git | python -c "from sys import stdin; print(stdin.read().split()[1])"`
export TEST_HOST=`docker network inspect $TEST_NETWORK | jq '.[0].IPAM.Config[0].Gateway' | sed 's/"//g'`
LIVE=False poetry run coverage run --source=../canarytokens --omit=integration/test_custom_binary.py -m pytest integration --runv3
LIVE=False poetry run coverage run --source=../canarytokens --omit=integration/test_custom_binary.py -m pytest integration --runv3 -v

- name: Run integration tests (against V2)
# Here we gather coverage info on integration tests.
run: |
cd tests
LIVE=True poetry run coverage run --source=integration --omit=integration/test_custom_binary.py -m pytest integration --runv2
LIVE=True poetry run coverage run --source=integration --omit=integration/test_custom_binary.py -m pytest integration --runv2 -v

- name: Check coverage is over threshold percentage for integration tests
# Here we check coverage info that all integration tests where indeed run.
Expand Down
4 changes: 3 additions & 1 deletion canarytokens/authenticode.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from canarytokens.sign_file import authenticode_sign_binary


def make_canary_authenticode_binary(nxdomain_token_url: str, filebody: bytes) -> bytes:
def make_canary_authenticode_binary(
nxdomain_token_url: str, filebody: bytes
) -> bytes: # pragma: no cover
"""Takes in a nxdomain url (eg: http://{token}.nxdomain.tools) and bytes string (some binary to sign)
and returns bytes (the signed binary).

Expand Down
11 changes: 2 additions & 9 deletions canarytokens/awskeys.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import logging
import re
from typing import Literal, Optional, TypedDict
from typing import Optional

import requests
from pydantic import HttpUrl

from canarytokens import tokens


class AWSKey(TypedDict):
access_key_id: str
secret_access_key: str
# TODO: make enum
region: str
output: Literal["json", "yaml", "yaml-stream", "text", "table"]
from canarytokens.models import AWSKey


def validate_record(server: str, token: tokens.Canarytoken) -> bool:
Expand Down
50 changes: 50 additions & 0 deletions canarytokens/azurekeys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import logging
from typing import Optional

import requests
from pydantic import HttpUrl

from canarytokens.models import AzureID
from canarytokens import tokens


def get_azure_id(
token: tokens.Canarytoken,
server: str,
cert_file_name: str,
azure_url: Optional[HttpUrl] = None,
app_id: Optional[str] = None,
cert: Optional[str] = None,
tenant_id: Optional[str] = None,
cert_name: Optional[str] = None,
) -> AzureID: # pragma: no cover
if app_id and cert and tenant_id and cert_name:
return AzureID(
**{
"app_id": app_id,
"cert": cert,
"tenant_id": tenant_id,
"cert_name": cert_name,
"cert_file_name": cert_file_name,
}
)
if not (token and server) or len(server) == 0:
logging.error("Empty values passed through to get_azure_id function.")
raise ValueError("get_azure_id requires token and server to be set.")
if not azure_url:
raise ValueError("get_azure_id requires azure_url to request from.")

data = {"token": token.value(), "domain": server}

resp = requests.post(url=azure_url, json=data)
resp.raise_for_status()
resp_json = resp.json()
return AzureID(
**{
"app_id": resp_json["app_id"],
"cert": resp_json["cert"],
"tenant_id": resp_json["tenant_id"],
"cert_name": resp_json["cert_name"],
"cert_file_name": cert_file_name,
}
)
8 changes: 8 additions & 0 deletions canarytokens/canarydrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ class Canarydrop(BaseModel):
aws_secret_access_key: Optional[str]
aws_output: Optional[str] = Field(alias="output")
aws_region: Optional[str] = Field(alias="region")

# Azure key specific stuff
app_id: Optional[str]
tenant_id: Optional[str]
cert: Optional[str]
cert_name: Optional[str]
cert_file_name: Optional[str]

# HTTP style token specific stuff
browser_scanner_enabled: Optional[bool]
# Wireguard specific stuff
Expand Down
5 changes: 5 additions & 0 deletions canarytokens/channel_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ def render_POST(self, request: Request):
canarydrop.add_canarydrop_hit(token_hit=token_hit)
self.dispatch(canarydrop=canarydrop, token_hit=token_hit)
return b"success"
elif canarydrop.type == TokenTypes.AZURE_ID:
token_hit = Canarytoken._parse_azure_id_trigger(request)
canarydrop.add_canarydrop_hit(token_hit=token_hit)
self.dispatch(canarydrop=canarydrop, token_hit=token_hit)
return b"success"
elif canarydrop.type in [
TokenTypes.SLOW_REDIRECT,
TokenTypes.WEB_IMAGE,
Expand Down
18 changes: 10 additions & 8 deletions canarytokens/extendtoken.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(
password,
card_name,
token=None,
):
): # pragma: no cover
self.email = email
self.token = token
self.kind = "AMEX"
Expand All @@ -45,7 +45,9 @@ def __init__(
self.token = req.json().get("token")
self.refresh_token = req.json().get("refresh_token")

def _post_api(self, endpoint: str, data: Optional[str] = None) -> requests.Response:
def _post_api(
self, endpoint: str, data: Optional[str] = None
) -> requests.Response: # pragma: no cover
"""Performs a POST against the passed endpoint with the data passed"""
headers = {
"Content-Type": "application/json",
Expand Down Expand Up @@ -163,7 +165,7 @@ def get_virtual_cards(self) -> list[tuple[str, str]]: # pragma: no cover
)
return cards

def get_card_info(self, card_id) -> Optional[dict[str, str]]:
def get_card_info(self, card_id) -> Optional[dict[str, str]]: # pragma: no cover
"""Returns all the data about a passed card_id available"""
req = self._get_api("https://v.paywithextend.com/virtualcards/" + card_id)
return req.json()
Expand Down Expand Up @@ -228,7 +230,7 @@ def make_card(
address: str,
billing_zip: str,
limit_cents: int = 100,
) -> CreditCard:
) -> CreditCard: # pragma: no cover
"""Creates a new CreditCard via Extend's CreateVirtualCard API"""
cc = self.get_parent_card_id()
now_ts = datetime.datetime.now() - datetime.timedelta(days=1)
Expand Down Expand Up @@ -283,7 +285,7 @@ def create_credit_card(
last_name: Optional[str] = None,
address: Optional[str] = None,
billing_zip: Optional[str] = None,
) -> CreditCard:
) -> CreditCard: # pragma: no cover
"""Creates a cardholder and associated virtual card for the passed person, if not passed, will generate fake data to use"""
fake_person = generate_person()
if first_name is None:
Expand All @@ -305,13 +307,13 @@ def create_credit_card(

return cc

def get_credit_card(self, id: str) -> CreditCard:
def get_credit_card(self, id: str) -> CreditCard: # pragma: no cover
"""Abstract method to get a virtual credit card"""
pass

def get_transaction_events( # noqa C901 # pragma: no cover
def get_transaction_events( # noqa C901
self, since: Optional[datetime.datetime] = None
) -> list:
) -> list: # pragma: no cover
"""Returns a list of recent transactions for the org"""
txns = []
req = self._get_api("https://api.paywithextend.com/events")
Expand Down
Loading