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

Add signer to support signing windows artifacts #2156

Merged
merged 17 commits into from
Jun 30, 2022
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
13 changes: 7 additions & 6 deletions jenkins/sign-artifacts/sign-standalone-artifacts.jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ pipeline {
description: 'Path to upload to artifacts and signatures on s3. Eg: dummy_project/1.0'
)
choice(
choices: ['linux'],
choices: ['linux', 'windows'],
name: 'DISTRIBUTION_PLATFORM',
description: 'What platform is this distribution build for?'
)
choice(
choices: ['.sig', '.rpm'],
name: 'SIGNATURE_TYPE',
description: 'What is signature file type?'
description: 'What is signature file type? Required only for linux signing.'
)
}
stages {
Expand All @@ -52,11 +52,13 @@ pipeline {
println("Note: only supported file types will be signed")

for(filename in downloadedFiles){
if (SIGNATURE_TYPE.equals('.sig')) {
if (DISTRIBUTION_PLATFORM == 'windows') {
filenamesForUrls.add(filename)
filenamesForUrls.add('signed/' + filename)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zelinh Please just remove the original file and upload the signed file like RPM in a new PR.
Then signed/signed structure is just very confusing.

Thanks.


File odfe_windows.exe can be accessed using the url - https://ci.opensearch.org/ci/dbc/Playground/zelin-sign-test/zelin-test/36/dist/signed/odfe_windows.exe
[Pipeline] echo
File signed/odfe_windows.exe can be accessed using the url - https://ci.opensearch.org/ci/dbc/Playground/zelin-sign-test/zelin-test/36/dist/signed/signed/odfe_windows.exe

} else if (SIGNATURE_TYPE.equals('.sig')) {
filenamesForUrls.add(filename)
filenamesForUrls.add(filename + SIGNATURE_TYPE)
}
else {
} else {
filenamesForUrls.add(filename)
}
}
Expand All @@ -80,7 +82,6 @@ pipeline {
artifactFileNames: filenamesForUrls,
uploadPath: finalUploadPath
)

}
}
post() {
Expand Down
3 changes: 1 addition & 2 deletions src/run_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from sign_workflow.sign_args import SignArgs
from sign_workflow.sign_artifacts import SignArtifacts
from sign_workflow.signer import Signer
from system import console


Expand All @@ -24,7 +23,7 @@ def main() -> int:
components=args.components,
artifact_type=args.type,
signature_type=args.sigtype,
signer=Signer()
platform=args.platform
)

sign.sign()
Expand Down
5 changes: 3 additions & 2 deletions src/sign_workflow/sign_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@


class SignArgs:
ACCEPTED_SIGNATURE_FILE_TYPES = [".sig"]
ACCEPTED_SIGNATURE_FILE_TYPES = [".sig", ".asc"]
ACCEPTED_PLATFORM = ["linux", "windows"]

target: Path
components: List[str]
Expand All @@ -25,7 +26,7 @@ def __init__(self) -> None:
parser.add_argument("-c", "--component", type=str, nargs='*', dest="components", help="Component or components to sign")
parser.add_argument("--type", help="Artifact type")
parser.add_argument("--sigtype", choices=self.ACCEPTED_SIGNATURE_FILE_TYPES, help="Type of signature file.", default=".asc")
parser.add_argument("--platform", nargs="?", help="Distribution platform.", default="linux")
parser.add_argument("--platform", choices=self.ACCEPTED_PLATFORM, help="Distribution platform.", default="linux")
parser.add_argument(
"-v",
"--verbose",
Expand Down
11 changes: 7 additions & 4 deletions src/sign_workflow/sign_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,24 @@

from manifests.build_manifest import BuildManifest
from sign_workflow.signer import Signer
from sign_workflow.signers import Signers


class SignArtifacts:
target: Path
component: str
artifact_type: str
signature_type: str
platform: str
signer: Signer

def __init__(self, target: Path, components: List[str], artifact_type: str, signature_type: str, signer: Signer) -> None:
def __init__(self, target: Path, components: List[str], artifact_type: str, signature_type: str, platform: str) -> None:
self.target = target
self.components = components
self.artifact_type = artifact_type
self.signature_type = signature_type
self.signer = signer
self.platform = platform
self.signer = Signers.create(platform)

@abstractmethod
def __sign__(self) -> None:
Expand All @@ -54,9 +57,9 @@ def __signer_class__(self, path: Path) -> Type[Any]:
return SignArtifactsExistingArtifactFile

@classmethod
def from_path(self, path: Path, components: List[str], artifact_type: str, signature_type: str, signer: Signer) -> Any:
def from_path(self, path: Path, components: List[str], artifact_type: str, signature_type: str, platform: str) -> Any:
klass = self.__signer_class__(path)
return klass(path, components, artifact_type, signature_type, signer)
return klass(path, components, artifact_type, signature_type, platform)


class SignWithBuildManifest(SignArtifacts):
Expand Down
40 changes: 9 additions & 31 deletions src/sign_workflow/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,16 @@

import logging
import os
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List

from git.git_repository import GitRepository

"""
This class is responsible for signing an artifact using the OpenSearch-signer-client and verifying its signature.
The signed artifacts will be found in the same location as the original artifacts.
"""


class Signer:
class Signer(ABC):
git_repo: GitRepository

ACCEPTED_FILE_TYPES = [".zip", ".jar", ".war", ".pom", ".module", ".tar.gz", ".whl", ".crate", ".rpm"]

def __init__(self) -> None:
self.git_repo = GitRepository(self.get_repo_url(), "HEAD", working_subdirectory="src")
self.git_repo.execute("./bootstrap")
Expand All @@ -42,15 +36,13 @@ def sign_artifacts(self, artifacts: List[str], basepath: Path, signature_type: s
continue
self.generate_signature_and_verify(artifact, basepath, signature_type)

@abstractmethod
def generate_signature_and_verify(self, artifact: str, basepath: Path, signature_type: str) -> None:
location = os.path.join(basepath, artifact)
self.sign(location, signature_type)
self.verify(location + signature_type)
pass

@abstractmethod
def is_valid_file_type(self, file_name: str) -> bool:
return any(
file_name.endswith(x) for x in Signer.ACCEPTED_FILE_TYPES
)
pass

def get_repo_url(self) -> str:
if "GITHUB_TOKEN" in os.environ:
Expand All @@ -62,20 +54,6 @@ def __remove_existing_signature__(self, signature_file: str) -> None:
logging.warning(f"Removing existing signature file {signature_file}")
os.remove(signature_file)

def sign(self, filename: str, signature_type: str) -> None:
signature_file = filename + signature_type
self.__remove_existing_signature__(signature_file)
signing_cmd = [
"./opensearch-signer-client",
"-i",
filename,
"-o",
signature_file,
"-p",
"pgp",
]
self.git_repo.execute(" ".join(signing_cmd))

def verify(self, filename: str) -> None:
verify_cmd = ["gpg", "--verify-files", filename]
self.git_repo.execute(" ".join(verify_cmd))
@abstractmethod
def sign(self, artifact: str, basepath: Path, signature_type: str) -> None:
pass
51 changes: 51 additions & 0 deletions src/sign_workflow/signer_pgp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python

# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os
from pathlib import Path

from sign_workflow.signer import Signer

"""
This class is responsible for signing an artifact using the OpenSearch-signer-client and verifying its signature.
The signed artifacts will be found in the same location as the original artifacts.
"""


class SignerPGP(Signer):

ACCEPTED_FILE_TYPES = [".zip", ".jar", ".war", ".pom", ".module", ".tar.gz", ".whl", ".crate", ".rpm"]

def generate_signature_and_verify(self, artifact: str, basepath: Path, signature_type: str) -> None:
location = os.path.join(basepath, artifact)
self.sign(artifact, basepath, signature_type)
self.verify(location + signature_type)

def is_valid_file_type(self, file_name: str) -> bool:
return any(
file_name.endswith(x) for x in SignerPGP.ACCEPTED_FILE_TYPES
)

def sign(self, artifact: str, basepath: Path, signature_type: str) -> None:
filename = os.path.join(basepath, artifact)
signature_file = filename + signature_type
self.__remove_existing_signature__(signature_file)
signing_cmd = [
"./opensearch-signer-client",
"-i",
filename,
"-o",
signature_file,
"-p",
"pgp",
]
self.git_repo.execute(" ".join(signing_cmd))

def verify(self, filename: str) -> None:
verify_cmd = ["gpg", "--verify-files", filename]
self.git_repo.execute(" ".join(verify_cmd))
51 changes: 51 additions & 0 deletions src/sign_workflow/signer_windows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python

# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os
from pathlib import Path

from sign_workflow.signer import Signer

"""
This class is responsible for signing an artifact using the OpenSearch-signer-client and verifying its signature.
The signed artifacts will be found in the subfolder called signed under the origin location as the original artifacts.
"""


class SignerWindows(Signer):

ACCEPTED_FILE_TYPES = [".msi", ".exe", ".dll", ".sys", ".ps1", ".psm1", ".psd1", ".cat", ".zip"]

def generate_signature_and_verify(self, artifact: str, basepath: Path, signature_type: str) -> None:
self.sign(artifact, basepath, signature_type)

def is_valid_file_type(self, file_name: str) -> bool:
return any(
file_name.endswith(x) for x in SignerWindows.ACCEPTED_FILE_TYPES
)

def sign(self, artifact: str, basepath: Path, signature_type: str) -> None:
filename = os.path.join(basepath, artifact)
signed_prefix = "signed_"
signature_file = os.path.join(basepath, signed_prefix + artifact)
self.__remove_existing_signature__(signature_file)
signing_cmd = [
"./opensearch-signer-client",
"-i",
filename,
"-o",
signature_file,
"-p",
"windows",
]
self.git_repo.execute(" ".join(signing_cmd))
signed_folder = os.path.join(basepath, "signed")
if not os.path.exists(signed_folder):
os.mkdir(signed_folder)
signed_location = os.path.join(signed_folder, artifact)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zelinh please create an issue to add a verification of the windows signing.

os.rename(signature_file, signed_location)
31 changes: 31 additions & 0 deletions src/sign_workflow/signers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python

# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.


from sign_workflow.signer import Signer
from sign_workflow.signer_pgp import SignerPGP
from sign_workflow.signer_windows import SignerWindows


class Signers:
TYPES = {
"windows": SignerWindows,
"linux": SignerPGP,
}

@classmethod
def from_platform(cls, platform: str) -> Signer:
klass = cls.TYPES.get(platform, None)
if not klass:
raise ValueError(f"Unsupported type of platform for signing: {platform}")
return klass # type: ignore[return-value]

@classmethod
def create(cls, platform: str) -> Signer:
klass = cls.from_platform(platform)
return klass() # type: ignore[no-any-return, operator]
6 changes: 3 additions & 3 deletions tests/jenkins/TestPromoteArtifacts.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ class TestPromoteArtifacts extends BuildPipelineTest {
binding.setVariable('DISTRIBUTION_ARCHITECTURE', 'x64')
binding.setVariable('WORKSPACE', 'tests/jenkins')
binding.setVariable('GITHUB_BOT_TOKEN_NAME', 'github_bot_token_name')
def signer_client_creds = ["role": "dummy_role",
def configs = ["role": "dummy_role",
"external_id": "dummy_ID",
"unsigned_bucket": "dummy_unsigned_bucket",
"signed_bucket": "dummy_signed_bucket"]
binding.setVariable('configs', signer_client_creds)
helper.registerAllowedMethod("readJSON", [Map.class], {c -> signer_client_creds})
binding.setVariable('configs', configs)
helper.registerAllowedMethod("readJSON", [Map.class], {c -> configs})

helper.registerAllowedMethod("git", [Map])
helper.registerAllowedMethod("s3Download", [Map])
Expand Down
6 changes: 3 additions & 3 deletions tests/jenkins/TestPromoteYumRepos.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ class TestPromoteYumRepos extends BuildPipelineTest {

binding.setVariable('PUBLIC_ARTIFACT_URL', 'https://ci.opensearch.org/dbc')
binding.setVariable('GITHUB_BOT_TOKEN_NAME', 'github_bot_token_name')
def signer_client_creds = ["role": "dummy_role",
def configs = ["role": "dummy_role",
"external_id": "dummy_ID",
"unsigned_bucket": "dummy_unsigned_bucket",
"signed_bucket": "dummy_signed_bucket"]
binding.setVariable('configs', signer_client_creds)
helper.registerAllowedMethod("readJSON", [Map.class], {c -> signer_client_creds})
binding.setVariable('configs', configs)
helper.registerAllowedMethod("readJSON", [Map.class], {c -> configs})
helper.registerAllowedMethod("git", [Map])
helper.registerAllowedMethod("withCredentials", [Map, Closure], { args, closure ->
closure.delegate = delegate
Expand Down
Loading