Repository: certbot/certbot
Pull request: Add Signed Windows Installer Workflow
This PR updates the Windows installer to be signed and trusted in Windows, by modifying the release workflow to integrate the new code signing server.

## Pull Request Checklist

- [x] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `master` section of `certbot/CHANGELOG.md` to include a description of the change being made.
- [x] Add or update any documentation as needed to support the changes in this PR.
- [x] Include your name in `AUTHORS.md` if you like.


Problem statement:
Make the Certbot Windows installer trusted by Windows
To do this, we need to securely generate and distribute a new signing key, obtain an authenticode certificate using that key, and then update the release process with instructions/scripts on how to perform this signature.

Hints from issue discussion:
I think we should do this soonish and I may move this back to this milestone, but the milestone is currently pretty stuffed and I don't think we MUST do this this month so I'm kicking this to the next milestone for now.

Commit message: Amend Chnagelog and Remove Unneeded Deps

Patch to review:
diff --git a/AUTHORS.md b/AUTHORS.md
index d82ddcb7688..962fbd6d9dd 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -17,6 +17,7 @@ Authors
 * [Alex Halderman](https://github.com/jhalderm)
 * [Alex Jordan](https://github.com/strugee)
 * [Alex Zorin](https://github.com/alexzorin)
+* [Alexis Hancock](https://github.com/zoracon)
 * [Amjad Mashaal](https://github.com/TheNavigat)
 * [Andrew Murray](https://github.com/radarhere)
 * [Andrzej Górski](https://github.com/andrzej3393)
diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md
index 29ccd5913c4..3790892b301 100644
--- a/certbot/CHANGELOG.md
+++ b/certbot/CHANGELOG.md
@@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
 
 ### Added
 
-*
+* Made our Windows installer signed and trusted in Windows 
 
 ### Changed
 
diff --git a/certbot/setup.py b/certbot/setup.py
index a8f32dc6e81..9ec85ea961d 100644
--- a/certbot/setup.py
+++ b/certbot/setup.py
@@ -65,9 +65,7 @@ def read_file(filename, encoding='utf8'):
 ]
 
 dev_extras = [
-    'azure-devops',
     'ipdb',
-    'PyGithub',
     # poetry 1.2.0+ is required for it to pin pip, setuptools, and wheel. See
     # https://github.com/python-poetry/poetry/issues/1584.
     'poetry>=1.2.0a1',
diff --git a/tools/finish_release.py b/tools/finish_release.py
index aa224e38ff8..478cc7d0486 100755
--- a/tools/finish_release.py
+++ b/tools/finish_release.py
@@ -8,17 +8,13 @@
 * Publishing the Windows installer in a GitHub release
 
 Setup:
- - Create a github personal access token
-   - https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token#creating-a-token
-   - You'll need repo scope
-   - Save the token to somewhere like ~/.ssh/githubpat.txt
  - Install the snapcraft command line tool and log in to a privileged account.
    - https://snapcraft.io/docs/installing-snapcraft
    - Use the command `snapcraft login` to log in.
 
 Run:
 
-python tools/finish_release.py ~/.ssh/githubpat.txt
+python tools/finish_release.py
 
 Testing:
 
@@ -36,10 +32,9 @@
 import subprocess
 import sys
 import tempfile
+import getpass
 from zipfile import ZipFile
 
-from azure.devops.connection import Connection
-from github import Github
 import requests
 
 # Path to the root directory of the Certbot repository containing this script
@@ -68,7 +63,7 @@ def parse_args(args):
     # Use the file's docstring for the help text and don't let argparse reformat it.
     parser = argparse.ArgumentParser(description=__doc__,
                                      formatter_class=argparse.RawDescriptionHelpFormatter)
-    parser.add_argument('githubpat', help='path to your GitHub personal access token')
+    parser.add_argument('--css', type=str, required=True, help='hostname of code signing server')
     group = parser.add_mutually_exclusive_group()
     # We use 'store_false' and a destination related to the other type of
     # artifact to cause the flag being set to disable publishing of the other
@@ -80,63 +75,19 @@ def parse_args(args):
                         help='Skip publishing other artifacts and only publish the Windows installer')
     return parser.parse_args(args)
 
+    
+def publish_windows(css):
+    """SSH into CSS and trigger downloading Azure Pipeline assets, sign, and upload to Github
 
-def download_azure_artifacts(tempdir):
-    """Download and unzip build artifacts from Azure pipelines.
-
-    :param str path: path to a temporary directory to save the files
-
-    :returns: released certbot version number as a prefix-free string
-    :rtype: str
-
-    """
-    # Create a connection to the azure org
-    organization_url = 'https://dev.azure.com/certbot'
-    connection = Connection(base_url=organization_url)
-
-    # Find the build artifacts
-    build_client = connection.clients.get_build_client()
-    get_builds_response = build_client.get_builds('certbot', definitions='3')
-    build_id = get_builds_response.value[0].id
-    artifacts = build_client.get_artifacts('certbot', build_id)
-
-    # Save and unzip files
-    for filename in ('windows-installer', 'changelog'):
-        print("Downloading artifact %s" % filename)
-        url = build_client.get_artifact('certbot', build_id, filename).resource.download_url
-        r = requests.get(url)
-        r.raise_for_status()
-        with open(tempdir + '/' + filename + '.zip', 'wb') as f:
-            f.write(r.content)
-        print("Extracting %s" % filename)
-        with ZipFile(tempdir + '/' + filename + '.zip', 'r') as zipObj:
-           zipObj.extractall(tempdir)
-
-    version = build_client.get_build('certbot', build_id).source_branch.split('v')[1]
-    return version
-
-def create_github_release(github_access_token, tempdir, version):
-    """Use build artifacts to create a github release, including uploading additional assets
-
-    :param str github_access_token: string containing github access token
-    :param str path: path to a temporary directory where azure artifacts are located
-    :param str version: Certbot version number, e.g. 1.7.0
+    :param str css: CSS host name
 
     """
-    # Create release
-    g = Github(github_access_token)
-    repo = g.get_user('certbot').get_repo('certbot')
-    release_notes = open(tempdir + '/changelog/release_notes.md', 'r').read()
-    print("Creating git release")
-    release= repo.create_git_release('v{0}'.format(version),
-                                     'Certbot {0}'.format(version),
-                                     release_notes,
-                                     draft=True)
-
-    # Upload windows installer to release
-    print("Uploading windows installer")
-    release.upload_asset(tempdir + '/windows-installer/certbot-beta-installer-win_amd64.exe')
-    release.update_release(release.title, release.body, draft=False)
+    username = getpass.getuser()
+    host = css
+    command = ["ssh", username + "@" + host, "python3", "sign.py", "~/.ssh/githubpat.txt"]
+    
+    print("SSH into CSS to trigger signing and uploading of Windows installer...")
+    subprocess.run(command.split(), check=True)
 
 
 def assert_logged_into_snapcraft():
@@ -216,23 +167,19 @@ def promote_snaps(version):
                 print(e.stdout)
                 raise
 
-
 def main(args):
     parsed_args = parse_args(args)
 
-    github_access_token_file = parsed_args.githubpat
-    github_access_token = open(github_access_token_file, 'r').read().rstrip()
-
-    with tempfile.TemporaryDirectory() as tempdir:
-        version = download_azure_artifacts(tempdir)
-        # Once the GitHub release has been published, trying to publish it
-        # again fails. Publishing the snaps can be done multiple times though
-        # so we do that first to make it easier to run the script again later
-        # if something goes wrong.
-        if parsed_args.publish_snaps:
-            promote_snaps(version)
-        if parsed_args.publish_windows:
-            create_github_release(github_access_token, tempdir, version)
+    css = parsed_args.css
+
+    # Once the GitHub release has been published, trying to publish it
+    # again fails. Publishing the snaps can be done multiple times though
+    # so we do that first to make it easier to run the script again later
+    # if something goes wrong.        
+    if parsed_args.publish_snaps:
+        promote_snaps(version)
+    if parsed_args.publish_windows:
+        publish_windows(css)
 
 if __name__ == "__main__":
     main(sys.argv[1:])


Code review comments:
diff_hunk: @@ -216,23 +167,19 @@ def promote_snaps(version):
                 print(e.stdout)
                 raise
 
-
 def main(args):
     parsed_args = parse_args(args)
 
-    github_access_token_file = parsed_args.githubpat
-    github_access_token = open(github_access_token_file, 'r').read().rstrip()
-
-    with tempfile.TemporaryDirectory() as tempdir:
-        version = download_azure_artifacts(tempdir)
-        # Once the GitHub release has been published, trying to publish it
-        # again fails. Publishing the snaps can be done multiple times though
-        # so we do that first to make it easier to run the script again later
-        # if something goes wrong.
-        if parsed_args.publish_snaps:
-            promote_snaps(version)
-        if parsed_args.publish_windows:
-            create_github_release(github_access_token, tempdir, version)
+    css = parsed_args.css
+
+    # Once the GitHub release has been published, trying to publish it
+    # again fails. Publishing the snaps can be done multiple times though
+    # so we do that first to make it easier to run the script again later
+    # if something goes wrong.        
+    if parsed_args.publish_snaps:
+        promote_snaps(version)
path: tools/finish_release.py
text: @user1:
Have you not had a chance to test this yourself yet? This script crashes for me on this line because `version` is no longer defined. To fix that, maybe we could make a simple function called something like `fetch_version_number` that contains the code to grab the version number from Azure Pipelines?

diff_hunk: @@ -8,17 +8,13 @@
 * Publishing the Windows installer in a GitHub release
 
 Setup:
- - Create a github personal access token
-   - https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token#creating-a-token
-   - You'll need repo scope
-   - Save the token to somewhere like ~/.ssh/githubpat.txt
  - Install the snapcraft command line tool and log in to a privileged account.
    - https://snapcraft.io/docs/installing-snapcraft
    - Use the command `snapcraft login` to log in.
 
 Run:
 
-python tools/finish_release.py ~/.ssh/githubpat.txt
+python tools/finish_release.py
path: tools/finish_release.py
text: @user1:
```suggestion
python tools/finish_release.py --css <URL of code signing server>
```