Skip to content

Commit

Permalink
Add signer to support signing windows artifacts (#2156)
Browse files Browse the repository at this point in the history
* Add signer for windows distribution and implement the jenkins libarary to be capable of signing for windows.

Signed-off-by: Zelin Hao <[email protected]>

* Replace platform with MagicMock for testing

Signed-off-by: Zelin Hao <[email protected]>

* Change to not initiate signer for mock case

Signed-off-by: Zelin Hao <[email protected]>

* Change the default signature type to .asc for compatibility with old usage

Signed-off-by: Zelin Hao <[email protected]>

* Add signer abstract class

Signed-off-by: Zelin Hao <[email protected]>

* Remove abstract method

Signed-off-by: Zelin Hao <[email protected]>

* Commit test cases

Signed-off-by: Zelin Hao <[email protected]>

* Fix python tests

Signed-off-by: Zelin Hao <[email protected]>

* Remove unused library import

Signed-off-by: Zelin Hao <[email protected]>

* Fix mock repo tests

Signed-off-by: Zelin Hao <[email protected]>

* Remove commmented block

Signed-off-by: Zelin Hao <[email protected]>

* Fix the python tests and combine credentials

Signed-off-by: Zelin Hao <[email protected]>

* Change path for windows tests

Signed-off-by: Zelin Hao <[email protected]>
  • Loading branch information
zelinh authored Jun 30, 2022
1 parent a2c2d3a commit 700c80d
Show file tree
Hide file tree
Showing 27 changed files with 658 additions and 397 deletions.
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)
} 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)
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

0 comments on commit 700c80d

Please sign in to comment.