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

Cert rotator #4617

Merged
merged 24 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 7 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: 8 additions & 0 deletions scripts/performance/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import os
import sys
import time
import base64
from typing import Callable, List, Optional, Tuple, Type, TypeVar


Expand All @@ -38,6 +39,9 @@ def get_machine_architecture():
def iswin():
return sys.platform == 'win32'

def islinux():
DrewScoggins marked this conversation as resolved.
Show resolved Hide resolved
return sys.platform == 'linux'

def extension():
'gets platform specific extension'
return '.exe' if iswin() else ''
Expand Down Expand Up @@ -139,6 +143,10 @@ def get_packages_directory() -> str:
'''
return os.path.join(get_artifacts_directory(), 'packages')

def base64_to_bytes(base64_string: str) -> bytes:
byte_data = base64.b64decode(base64_string)
return byte_data

@contextmanager
def push_dir(path: Optional[str] = None):
'''
Expand Down
3 changes: 2 additions & 1 deletion scripts/performance/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
UPLOAD_STORAGE_URI = 'https://pvscmdupload.{}.core.windows.net'
UPLOAD_QUEUE = 'resultsqueue'
TENANT_ID = '72f988bf-86f1-41af-91ab-2d7cd011db47'
CLIENT_ID = 'a231f733-103b-46e9-b58a-9416edde0eb4'
ARC_CLIENT_ID = 'a231f733-103b-46e9-b58a-9416edde0eb4'
CERT_CLIENT_ID = '8c4b65ef-5a73-4d5a-a298-962d4a4ef7bc'
37 changes: 26 additions & 11 deletions scripts/run_performance_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,32 @@ def run_performance_job(args: RunPerformanceJobArgs):
getLogger().info("Copying global.json to payload directory")
shutil.copy(os.path.join(args.performance_repo_dir, 'global.json'), os.path.join(performance_payload_dir, 'global.json'))

# Building CertHelper needs to happen here as we need it on every run. This also means that we will need to move the calculation
# of the parameters needed outside of the if block

framework = os.environ["PERFLAB_Framework"]
os.environ["PERFLAB_TARGET_FRAMEWORKS"] = framework
if args.os_group == "windows":
runtime_id = f"win-{args.architecture}"
elif args.os_group == "osx":
runtime_id = f"osx-{args.architecture}"
else:
runtime_id = f"linux-{args.architecture}"

dotnet_executable_path = os.path.join(ci_setup_arguments.install_dir, "dotnet")

RunCommand([
dotnet_executable_path, "publish",
"-c", "Release",
"-o", os.path.join(payload_dir, "certhelper"),
"-f", framework,
"-r", runtime_id,
"--self-contained",
os.path.join(args.performance_repo_dir, "src", "tools", "CertHelper", "CertHelper.csproj"),
f"/bl:{os.path.join(args.performance_repo_dir, 'artifacts', 'log', build_config, 'CertHelper.binlog')}",
"-p:DisableTransitiveFrameworkReferenceDownloads=true"],
verbose=True).run()

if args.is_scenario:
set_environment_variable("DOTNET_ROOT", ci_setup_arguments.install_dir, save_to_pipeline=True)
getLogger().info(f"Set DOTNET_ROOT to {ci_setup_arguments.install_dir}")
Expand All @@ -782,17 +808,6 @@ def run_performance_job(args: RunPerformanceJobArgs):
set_environment_variable("PATH", new_path, save_to_pipeline=True)
getLogger().info(f"Set PATH to {new_path}")

framework = os.environ["PERFLAB_Framework"]
os.environ["PERFLAB_TARGET_FRAMEWORKS"] = framework
if args.os_group == "windows":
runtime_id = f"win-{args.architecture}"
elif args.os_group == "osx":
runtime_id = f"osx-{args.architecture}"
else:
runtime_id = f"linux-{args.architecture}"

dotnet_executable_path = os.path.join(ci_setup_arguments.install_dir, "dotnet")

os.environ["MSBUILDDISABLENODEREUSE"] = "1" # without this, MSbuild will be kept alive

# build Startup
Expand Down
29 changes: 21 additions & 8 deletions scripts/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
from azure.storage.blob import BlobClient, ContentSettings
from azure.storage.queue import QueueClient, TextBase64EncodePolicy
from azure.core.exceptions import ResourceExistsError, ClientAuthenticationError
from azure.identity import DefaultAzureCredential, ClientAssertionCredential
from azure.identity import DefaultAzureCredential, ClientAssertionCredential, CertificateCredential
from traceback import format_exc
from glob import glob
from performance.common import retry_on_exception
from performance.constants import TENANT_ID, CLIENT_ID
from performance.common import retry_on_exception, iswin, islinux, RunCommand, helixpayload, base64_to_bytes
from performance.constants import TENANT_ID, ARC_CLIENT_ID, CERT_CLIENT_ID
import os
import json
import ssl
from cryptography import x509

from logging import getLogger

Expand All @@ -32,14 +34,25 @@ def upload(globpath: str, container: str, queue: str, sas_token_env: str, storag
credential = None
try:
dac = DefaultAzureCredential()
credential = ClientAssertionCredential(TENANT_ID, CLIENT_ID, lambda: dac.get_token("api://AzureADTokenExchange/.default").token)
credential = ClientAssertionCredential(TENANT_ID, ARC_CLIENT_ID, lambda: dac.get_token("api://AzureADTokenExchange/.default").token)
credential.get_token("https://storage.azure.com/.default")
except ClientAuthenticationError as ex:
getLogger().info("Unable to use managed identity. Falling back to environment variable.")
credential = os.getenv(sas_token_env)
credential = None
getLogger().info("Unable to use managed identity. Falling back to certificate.")
cmd_line = list((os.path.join(str(helixpayload()), 'certhelper', 'CertHelper.exe')))
DrewScoggins marked this conversation as resolved.
Show resolved Hide resolved
cert_helper = RunCommand(cmd_line, None, False, 0)
cert_helper.run()
for cert in cert_helper.stdout.splitlines():
credential = CertificateCredential(TENANT_ID, CERT_CLIENT_ID, certificate_data=base64_to_bytes(cert))
try:
credential.get_token("https://storage.azure.com/.default")
except ClientAuthenticationError as ex:
credential = None
continue
if credential is None:
getLogger().error("Sas token environment variable {} was not defined.".format(sas_token_env))
return 1
getLogger().error("Unable to authenticate with managed identity or certificates.")
getLogger().info("Falling back to environment variable.")
credential = os.getenv(sas_token_env)
LoopedBard3 marked this conversation as resolved.
Show resolved Hide resolved

files = glob(globpath, recursive=True)
any_upload_or_queue_failed = False
Expand Down
10 changes: 10 additions & 0 deletions src/tools/CertHelper/CertHelper.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
LoopedBard3 marked this conversation as resolved.
Show resolved Hide resolved
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
22 changes: 22 additions & 0 deletions src/tools/CertHelper/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace CertHelper
{
internal class Program
{
static void Main(string[] args)
{
using(var store = new X509Store(StoreName.My, StoreLocation.CurrentUser, OpenFlags.ReadWrite))
{
foreach(var cert in store.Certificates)
DrewScoggins marked this conversation as resolved.
Show resolved Hide resolved
{
if (cert.Subject.Contains("CN=dotnetperf.microsoft.com"))
{
Console.WriteLine(Convert.ToBase64String(cert.Export(X509ContentType.Pfx)));
}
}
}
}
}
}
Loading