Move reposerver tests to micromamba (#2941)

* Move reposerver tests to micromamba

* Add micromamba channel data

* Remove mamba test

* Fix mamba path

* Restore mamba files

* Fix mamba code
This commit is contained in:
Antoine Prouvost 2023-10-26 22:17:48 +02:00 committed by GitHub
parent bca398a5dc
commit a38196e284
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1206 additions and 2 deletions

View File

@ -29,6 +29,10 @@ dependencies:
- pytest-lazy-fixture
- pytest-xprocess
- sel(win): pywin32
- conda-content-trust
- cryptography<40.0 # Or breaks conda-content-trust
- pip:
- securesystemslib
# libmambapy build dependencies
- pybind11-stubgen <1.0
# libmambapy dependencies

View File

@ -0,0 +1,53 @@
{
"info": {
"subdir": "linux-64"
},
"packages": {
"A_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.1.0"
},
"A_0.2.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.2.0"
},
"B_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
"a"
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "b",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,55 @@
{
"info": {
"subdir": "linux-64"
},
"packages": {
"A_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.1.0"
},
"A_0.2.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.2.0"
},
"B_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
"a"GLIBC_PLACEHOLDER
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "b",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,40 @@
{
"info": {
"subdir": "noarch"
},
"packages": {
"_r-mutex-1.0.1-anacondar_1.tar.bz2": {
"build": "anacondar_1",
"build_number": 1,
"constrains": [],
"depends": [],
"license": "BSD",
"md5": "19f9db5f4f1b7f5ef5f6d67207f25f38",
"name": "_r-mutex",
"noarch": "generic",
"platform": null,
"sha256": "e58f9eeb416b92b550e824bcb1b9fb1958dee69abfe3089dfd1a9173e3a0528a",
"size": 3566,
"subdir": "noarch",
"timestamp": 1562343890778,
"track_features": "",
"version": "1.0.1"
},
"testpkg_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "testpkg",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "noarch",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,53 @@
{
"info": {
"subdir": "win-64"
},
"packages": {
"A_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.1.0"
},
"A_0.2.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.2.0"
},
"B_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
"a"
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "b",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,55 @@
{
"info": {
"subdir": "win-64"
},
"packages": {
"A_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.1.0"
},
"A_0.2.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.2.0"
},
"B_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [
"a"GLIBC_PLACEHOLDER
],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "b",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,23 @@
{
"info": {
"subdir": "linux-64"
},
"packages": {
"A_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "linux-64",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,23 @@
{
"info": {
"subdir": "win-64"
},
"packages": {
"A_0.1.0.tar.bz2": {
"build": "abc",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "85107fc10154734ef34a5a75685be684",
"name": "a",
"sha256": "398831eff682d2c975b360d64656d8f475cbc1f1b6d0ee33d86285190e7ee4d1",
"size": 222503,
"subdir": "win-64",
"timestamp": 1578950023135,
"version": "0.1.0"
}
},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,31 @@
#!/bin/bash
set -euo pipefail -x
cat << EOF | gpg --batch --generate-key
%no-protection
Key-Type: eddsa
Key-Curve: Ed25519
Key-Usage: sign
Name-Real: MAMBA1
Name-Email: mail at example.com
Creation-Date: 20170801T180000
Expire-Date: 0
Subkey-Type: ecdh
Subkey-Curve: Curve25519
Subkey-Usage: encrypt
EOF
cat << EOF | gpg --batch --generate-key
%no-protection
Key-Type: eddsa
Key-Curve: Ed25519
Key-Usage: sign
Name-Real: MAMBA2
Name-Email: mail at example.com
Creation-Date: 20170801T180000
Expire-Date: 0
Subkey-Type: ecdh
Subkey-Curve: Curve25519
Subkey-Usage: encrypt
EOF

View File

@ -0,0 +1,38 @@
{
"channeldata_version": 1,
"packages": {
"test-package": {
"activate.d": false,
"binary_prefix": false,
"deactivate.d": false,
"description": null,
"dev_url": null,
"doc_source_url": null,
"doc_url": null,
"home": "https://github.com/mamba-org/mamba",
"icon_hash": null,
"icon_url": null,
"identifiers": null,
"keywords": null,
"license": "BSD",
"post_link": false,
"pre_link": false,
"pre_unlink": false,
"recipe_origin": null,
"run_exports": {},
"source_git_url": null,
"source_url": null,
"subdirs": [
"noarch"
],
"summary": "I am just a test package!",
"tags": null,
"text_prefix": false,
"timestamp": 1613117294,
"version": "0.1"
}
},
"subdirs": [
"noarch"
]
}

View File

@ -0,0 +1,90 @@
<html>
<head>
<title>repo</title>
<style type="text/css">
a, a:active {
text-decoration: none; color: blue;
}
a:visited {
color: #48468F;
}
a:hover, a:focus {
text-decoration: underline; color: red;
}
body {
background-color: #F5F5F5;
}
h2 {
margin-bottom: 12px;
}
th, td {
font: 100% monospace; text-align: left;
}
th {
font-weight: bold; padding-right: 14px; padding-bottom: 3px;
}
th.tight {
padding-right: 6px;
}
td {
padding-right: 14px;
}
td.tight {
padding-right: 8px;
}
td.s, th.s {
text-align: right;
}
td.summary {
white-space: nowrap;
overflow: hidden;
}
td.packagename {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 180px;
padding-right: 8px;
}
td.version {
//white-space: nowrap;
overflow: hidden;
max-width: 90px;
padding-right: 8px;
}
table {
background-color: white;
border-top: 1px solid #646464;
border-bottom: 1px solid #646464;
padding-top: 10px;
padding-bottom: 14px;
}
address {
color: #787878;
padding-top: 10px;
}
</style>
</head>
<body>
<h2>repo</h2>
<h3><a href="rss.xml">RSS Feed</a>&nbsp;&nbsp;&nbsp;<a href="channeldata.json">channeldata.json</a></h3>
<a href="noarch">noarch</a>&nbsp;&nbsp;&nbsp; <table>
<tr>
<th style="padding-right:18px;">Package</th>
<th>Latest Version</th>
<th>Doc</th>
<th>Dev</th>
<th>License</th>
<th class="tight">noarch</th> <th>Summary</th>
</tr>
<tr>
<td class="packagename"><a href="https://github.com/mamba-org/mamba" alt="test-package">test-package</a></td>
<td class="version">0.1</td>
<td></td>
<td></td>
<td class="tight">BSD</td>
<td>X</td> <td class="summary">I am just a test package!</td>
</tr> </table>
<address>Updated: 2021-02-12 09:02:37 +0000 - Files: 1</address>
</body>
</html>

View File

@ -0,0 +1,25 @@
{
"info": {
"subdir": "noarch"
},
"packages": {
"test-package-0.1-0.tar.bz2": {
"build": "0",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "2a8595f37faa2950e1b433acbe91d481",
"name": "test-package",
"noarch": "generic",
"sha256": "b908ffce2d26d94c58c968abf286568d4bcf87d1cfe6c994958351724a6f6988",
"size": 5719,
"subdir": "noarch",
"timestamp": 1613117294885,
"version": "0.1"
}
},
"packages.conda": {},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,88 @@
<html>
<head>
<title>repo/noarch</title>
<style type="text/css">
a, a:active {
text-decoration: none; color: blue;
}
a:visited {
color: #48468F;
}
a:hover, a:focus {
text-decoration: underline; color: red;
}
body {
background-color: #F5F5F5;
}
h2 {
margin-bottom: 12px;
}
th, td {
font: 100% monospace; text-align: left;
}
th {
font-weight: bold; padding-right: 14px; padding-bottom: 3px;
}
td {
padding-right: 20px;
}
td.s, th.s {
text-align: right;
}
table {
background-color: white;
border-top: 1px solid #646464;
border-bottom: 1px solid #646464;
padding-top: 10px;
padding-bottom: 14px;
}
address {
color: #787878;
padding-top: 10px;
}
</style>
</head>
<body>
<h2>repo/noarch</h2>
<table>
<tr>
<th>Filename</th>
<th>Size</th>
<th>Last Modified</th>
<th>SHA256</th>
<th>MD5</th>
</tr>
<tr>
<td><a href="repodata.json" alt="repodata.json">repodata.json</a></td>
<td class="s">586 B</td>
<td>2021-02-12 09:01:48 +0000</td>
<td>cc5f72aaa8d3f508c8adca196fe05cf4b19e1ca1006cfcbb3892d73160bd3b04</td>
<td>7501ec77771889b42a39c615158cb9c4</td>
</tr> <tr>
<td><a href="repodata.json.bz2" alt="repodata.json.bz2">repodata.json.bz2</a></td>
<td class="s">351 B</td>
<td>2021-02-12 09:01:48 +0000</td>
<td>9a0288ca48c6b8caa348d7cafefd0981c2d25dcb4a5837a5187ab200b8b9fb45</td>
<td>0c926155642f0e894d97dc8a5af7007b</td>
</tr> <tr>
<td><a href="repodata_from_packages.json" alt="repodata_from_packages.json">repodata_from_packages.json</a></td>
<td class="s">586 B</td>
<td>2021-02-12 09:01:48 +0000</td>
<td>cc5f72aaa8d3f508c8adca196fe05cf4b19e1ca1006cfcbb3892d73160bd3b04</td>
<td>7501ec77771889b42a39c615158cb9c4</td>
</tr> <tr>
<td><a href="repodata_from_packages.json.bz2" alt="repodata_from_packages.json.bz2">repodata_from_packages.json.bz2</a></td>
<td class="s">351 B</td>
<td>2021-02-12 09:01:48 +0000</td>
<td>9a0288ca48c6b8caa348d7cafefd0981c2d25dcb4a5837a5187ab200b8b9fb45</td>
<td>0c926155642f0e894d97dc8a5af7007b</td>
</tr> <tr>
<td><a href="test-package-0.1-0.tar.bz2" alt="test-package-0.1-0.tar.bz2">test-package-0.1-0.tar.bz2</a></td>
<td class="s">6 KB</td>
<td>2021-02-12 08:08:14 +0000</td>
<td>b908ffce2d26d94c58c968abf286568d4bcf87d1cfe6c994958351724a6f6988</td>
<td>2a8595f37faa2950e1b433acbe91d481</td>
</tr> </table>
<address>Updated: 2021-02-12 09:02:37 +0000 - Files: 1</address>
</body>
</html>

View File

@ -0,0 +1,25 @@
{
"info": {
"subdir": "noarch"
},
"packages": {
"test-package-0.1-0.tar.bz2": {
"build": "0",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "2a8595f37faa2950e1b433acbe91d481",
"name": "test-package",
"noarch": "generic",
"sha256": "b908ffce2d26d94c58c968abf286568d4bcf87d1cfe6c994958351724a6f6988",
"size": 5719,
"subdir": "noarch",
"timestamp": 1613117294885,
"version": "0.1"
}
},
"packages.conda": {},
"removed": [],
"repodata_version": 1
}

Binary file not shown.

View File

@ -0,0 +1,25 @@
{
"info": {
"subdir": "noarch"
},
"packages": {
"test-package-0.1-0.tar.bz2": {
"build": "0",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "2a8595f37faa2950e1b433acbe91d481",
"name": "test-package",
"noarch": "generic",
"sha256": "b908ffce2d26d94c58c968abf286568d4bcf87d1cfe6c994958351724a6f6988",
"size": 5719,
"subdir": "noarch",
"timestamp": 1613117294885,
"version": "0.1"
}
},
"packages.conda": {},
"removed": [],
"repodata_version": 1
}

View File

@ -0,0 +1,14 @@
package:
name: test-package
version: 0.1
build:
number: 0
script: echo Hello world
noarch: generic
about:
home: https://github.com/mamba-org/mamba
license: BSD
license_family: BSD
summary: I am just a test package!

View File

@ -0,0 +1,29 @@
{
"signatures": {
"350689d2a4dff47a6c260be35dca2025f85a971695a13b25de5bd40d7801bbb0": {
"other_headers": "04001608001d162104860d71890caeafcbda51ce4539ccaa003193e2090502653a7512",
"signature": "b5a94a6d610763e6322b65550bb0f2c6f2c828150ece9b67d22511ca2569b6266e43393abe34a347072c689679c2f4ef3fb38c8325966049c9cfa9d3d6dcd702"
}
},
"signed": {
"delegations": {
"key_mgr": {
"pubkeys": [
"013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7"
],
"threshold": 1
},
"root": {
"pubkeys": [
"350689d2a4dff47a6c260be35dca2025f85a971695a13b25de5bd40d7801bbb0"
],
"threshold": 1
}
},
"expiration": "2024-10-25T14:17:54Z",
"metadata_spec_version": "0.6.0",
"timestamp": "2023-10-26T14:17:54Z",
"type": "root",
"version": 1
}
}

View File

@ -0,0 +1,22 @@
{
"signatures": {
"013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7": {
"signature": "0a4ee7615fb9115e5bbd62a1a7f33455e6c26d46e0e815a6015258ad17d41570c068444311eb165828670cc4275b5650fb03eb235d10062b6d98f2650d62c50b"
}
},
"signed": {
"delegations": {
"pkg_mgr": {
"pubkeys": [
"f46b5a7caa43640744186564c098955147daa8bac4443887bc64d8bfee3d3569"
],
"threshold": 1
}
},
"expiration": "2024-10-25T14:17:54Z",
"metadata_spec_version": "0.6.0",
"timestamp": "2023-10-26T14:17:54Z",
"type": "key_mgr",
"version": 1
}
}

View File

@ -0,0 +1,32 @@
{
"info": {
"subdir": "noarch"
},
"packages": {
"test-package-0.1-0.tar.bz2": {
"build": "0",
"build_number": 0,
"depends": [],
"license": "BSD",
"license_family": "BSD",
"md5": "2a8595f37faa2950e1b433acbe91d481",
"name": "test-package",
"noarch": "generic",
"sha256": "b908ffce2d26d94c58c968abf286568d4bcf87d1cfe6c994958351724a6f6988",
"size": 5719,
"subdir": "noarch",
"timestamp": 1613117294885,
"version": "0.1"
}
},
"packages.conda": {},
"removed": [],
"repodata_version": 1,
"signatures": {
"test-package-0.1-0.tar.bz2": {
"f46b5a7caa43640744186564c098955147daa8bac4443887bc64d8bfee3d3569": {
"signature": "0a50063539baf249970f1d08b07f00f544e2d87982826790e9ec6e80874ad90aec21a9607cf38bb58897163533c39cb4a4f1c741a7f8e9e4f67e2ff2087d2d00"
}
}
}
}

View File

@ -0,0 +1,392 @@
import argparse
import base64
import glob
import os
import re
import shutil
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from typing import Dict, List
try:
import conda_content_trust.authentication as cct_authentication
import conda_content_trust.common as cct_common
import conda_content_trust.metadata_construction as cct_metadata_construction
import conda_content_trust.root_signing as cct_root_signing
import conda_content_trust.signing as cct_signing
conda_content_trust_available = True
except ImportError:
conda_content_trust_available = False
def fatal_error(message: str) -> None:
"""Print error and exit."""
print(message, file=sys.stderr)
exit(1)
def get_fingerprint(gpg_output: str) -> str:
lines = gpg_output.splitlines()
fpline = lines[1].strip()
fpline = fpline.replace(" ", "")
return fpline
KeySet = Dict[str, List[Dict[str, str]]]
def normalize_keys(keys: KeySet) -> KeySet:
out = {}
for ik, iv in keys.items():
out[ik] = []
for el in iv:
if isinstance(el, str):
el = el.lower()
keyval = cct_root_signing.fetch_keyval_from_gpg(el)
res = {"fingerprint": el, "public": keyval}
elif isinstance(el, dict):
res = {
"private": el["private"].lower(),
"public": el["public"].lower(),
}
out[ik].append(res)
return out
class RepoSigner:
keys = {
"root": [],
"key_mgr": [
{
"private": "c9c2060d7e0d93616c2654840b4983d00221d8b6b69c850107da74b42168f937",
"public": "013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7",
},
],
"pkg_mgr": [
{
"private": "f3cdab14740066fb277651ec4f96b9f6c3e3eb3f812269797b9656074cd52133",
"public": "f46b5a7caa43640744186564c098955147daa8bac4443887bc64d8bfee3d3569",
}
],
}
def __init__(self, in_folder: str) -> None:
self.in_folder = Path(in_folder).resolve()
self.folder = self.in_folder.parent / (str(self.in_folder.name) + "_signed")
self.keys["root"] = [
get_fingerprint(os.environ["KEY1"]),
get_fingerprint(os.environ["KEY2"]),
]
self.keys = normalize_keys(self.keys)
def make_signed_repo(self) -> Path:
print("[reposigner] Using keys:", self.keys)
print("[reposigner] Using folder:", self.folder)
self.folder.mkdir(exist_ok=True)
self.create_root(self.keys)
self.create_key_mgr(self.keys)
for f in glob.glob(str(self.in_folder / "**" / "repodata.json")):
self.sign_repodata(Path(f), self.keys)
return self.folder
def create_root(self, keys):
root_keys = keys["root"]
root_pubkeys = [k["public"] for k in root_keys]
key_mgr_pubkeys = [k["public"] for k in keys["key_mgr"]]
root_version = 1
root_md = cct_metadata_construction.build_root_metadata(
root_pubkeys=root_pubkeys[0:1],
root_threshold=1,
root_version=root_version,
key_mgr_pubkeys=key_mgr_pubkeys,
key_mgr_threshold=1,
)
# Wrap the metadata in a signing envelope.
root_md = cct_signing.wrap_as_signable(root_md)
root_md_serialized_unsigned = cct_common.canonserialize(root_md)
root_filepath = self.folder / f"{root_version}.root.json"
print("Writing out: ", root_filepath)
# Write unsigned sample root metadata.
with open(root_filepath, "wb") as fout:
fout.write(root_md_serialized_unsigned)
# This overwrites the file with a signed version of the file.
cct_root_signing.sign_root_metadata_via_gpg(
root_filepath, root_keys[0]["fingerprint"]
)
# Load untrusted signed root metadata.
signed_root_md = cct_common.load_metadata_from_file(root_filepath)
cct_authentication.verify_signable(signed_root_md, root_pubkeys, 1, gpg=True)
print("[reposigner] Root metadata signed & verified!")
def create_key_mgr(self, keys):
private_key_key_mgr = cct_common.PrivateKey.from_hex(
keys["key_mgr"][0]["private"]
)
pkg_mgr_pub_keys = [k["public"] for k in keys["pkg_mgr"]]
key_mgr = cct_metadata_construction.build_delegating_metadata(
metadata_type="key_mgr", # 'root' or 'key_mgr'
delegations={"pkg_mgr": {"pubkeys": pkg_mgr_pub_keys, "threshold": 1}},
version=1,
# timestamp default: now
# expiration default: now plus root expiration default duration
)
key_mgr = cct_signing.wrap_as_signable(key_mgr)
# sign dictionary in place
cct_signing.sign_signable(key_mgr, private_key_key_mgr)
key_mgr_serialized = cct_common.canonserialize(key_mgr)
with open(self.folder / "key_mgr.json", "wb") as fobj:
fobj.write(key_mgr_serialized)
# let's run a verification
root_metadata = cct_common.load_metadata_from_file(self.folder / "1.root.json")
key_mgr_metadata = cct_common.load_metadata_from_file(
self.folder / "key_mgr.json"
)
cct_common.checkformat_signable(root_metadata)
if "delegations" not in root_metadata["signed"]:
raise ValueError('Expected "delegations" entry in root metadata.')
root_delegations = root_metadata["signed"]["delegations"] # for brevity
cct_common.checkformat_delegations(root_delegations)
if "key_mgr" not in root_delegations:
raise ValueError(
'Missing expected delegation to "key_mgr" in root metadata.'
)
cct_common.checkformat_delegation(root_delegations["key_mgr"])
# Doing delegation processing.
cct_authentication.verify_delegation("key_mgr", key_mgr_metadata, root_metadata)
print("[reposigner] success: key mgr metadata verified based on root metadata.")
return key_mgr
def sign_repodata(self, repodata_fn, keys):
target_folder = self.folder / repodata_fn.parent.name
if not target_folder.exists():
target_folder.mkdir()
final_fn = target_folder / repodata_fn.name
print("copy", repodata_fn, final_fn)
shutil.copyfile(repodata_fn, final_fn)
pkg_mgr_key = keys["pkg_mgr"][0]["private"]
cct_signing.sign_all_in_repodata(str(final_fn), pkg_mgr_key)
print(f"[reposigner] Signed {final_fn}")
class ChannelHandler(SimpleHTTPRequestHandler):
url_pattern = re.compile(r"^/(?:t/[^/]+/)?([^/]+)")
def do_GET(self) -> None:
# First extract channel name
channel_name = None
if tuple(channels.keys()) != (None,):
match = self.url_pattern.match(self.path)
if match:
channel_name = match.group(1)
# Strip channel for file server
start, end = match.span(1)
self.path = self.path[:start] + self.path[end:]
# Then dispatch to appropriate auth method
if channel_name in channels:
channel = channels[channel_name]
self.directory = channel["directory"]
auth = channel["auth"]
if auth == "none":
return SimpleHTTPRequestHandler.do_GET(self)
elif auth == "basic":
server_key = base64.b64encode(
bytes(f"{channel['user']}:{channel['password']}", "utf-8")
).decode("ascii")
return self.basic_do_GET(server_key=server_key)
elif auth == "bearer":
return self.bearer_do_GET(server_key=channel["bearer"])
elif auth == "token":
return self.token_do_GET(server_token=channel["token"])
self.send_response(404)
def basic_do_HEAD(self) -> None:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
def basic_do_AUTHHEAD(self) -> None:
self.send_response(401)
self.send_header("WWW-Authenticate", 'Basic realm="Test"')
self.send_header("Content-type", "text/html")
self.end_headers()
def bearer_do_GET(self, server_key: str) -> None:
auth_header = self.headers.get("Authorization", "")
print(auth_header)
print(f"Bearer {server_key}")
if not auth_header or auth_header != f"Bearer {server_key}":
self.send_response(403)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"no valid api key received")
else:
SimpleHTTPRequestHandler.do_GET(self)
def basic_do_GET(self, server_key: str) -> None:
"""Present frontpage with basic user authentication."""
auth_header = self.headers.get("Authorization", "")
if not auth_header:
self.basic_do_AUTHHEAD()
self.wfile.write(b"no auth header received")
elif auth_header == "Basic " + server_key:
SimpleHTTPRequestHandler.do_GET(self)
else:
self.basic_do_AUTHHEAD()
self.wfile.write(auth_header.encode("ascii"))
self.wfile.write(b"not authenticated")
token_pattern = re.compile("^/t/([^/]+?)/")
def token_do_GET(self, server_token: str) -> None:
"""Present frontpage with user authentication."""
match = self.token_pattern.search(self.path)
if match:
prefix_length = len(match.group(0)) - 1
new_path = self.path[prefix_length:]
found_token = match.group(1)
if found_token == server_token:
self.path = new_path
return SimpleHTTPRequestHandler.do_GET(self)
self.send_response(403)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"no valid api key received")
global_parser = argparse.ArgumentParser(
description="Start a multi-channel conda package server."
)
global_parser.add_argument("-p", "--port", type=int, default=8000, help="Port to use.")
channel_parser = argparse.ArgumentParser(
description="Start a simple conda package server."
)
channel_parser.add_argument(
"-d",
"--directory",
type=str,
default=os.getcwd(),
help="Root directory for serving.",
)
channel_parser.add_argument(
"-n",
"--name",
type=str,
default=None,
help="Unique name of the channel used in URL",
)
channel_parser.add_argument(
"-a",
"--auth",
default=None,
type=str,
help="auth method (none, basic, token, or bearer)",
)
channel_parser.add_argument(
"--sign",
action="store_true",
help="Sign repodata (note: run generate_gpg_keys.sh before)",
)
channel_parser.add_argument(
"--token",
type=str,
default=None,
help="Use token as API Key",
)
channel_parser.add_argument(
"--bearer",
type=str,
default=None,
help="Use bearer token as API Key",
)
channel_parser.add_argument(
"--user",
type=str,
default=None,
help="Use token as API Key",
)
channel_parser.add_argument(
"--password",
type=str,
default=None,
help="Use token as API Key",
)
# Gobal args can be given anywhere with the first set of args for backward compatibility.
args, argv_remaining = global_parser.parse_known_args()
PORT = args.port
# Iteratively parse arguments in sets.
# Each argument set, separated by -- in the CLI is for a channel.
# Credits: @hpaulj on SO https://stackoverflow.com/a/26271421
channels = {}
while argv_remaining:
args, argv_remaining = channel_parser.parse_known_args(argv_remaining)
# Drop leading -- to move to next argument set
argv_remaining = argv_remaining[1:]
# Consolidation
if not args.auth:
if args.user and args.password:
args.auth = "basic"
elif args.token:
args.auth = "token"
elif args.bearer:
args.auth = "bearer"
else:
args.auth = "none"
if args.sign:
if not conda_content_trust_available:
fatal_error("Conda content trust not installed!")
args.directory = RepoSigner(args.directory).make_signed_repo()
# name = args.name if args.name else Path(args.directory).name
# args.name = name
channels[args.name] = vars(args)
print(channels)
# Unamed channel in multi-channel case would clash URLs but we want to allow
# a single unamed channel for backward compatibility.
if (len(channels) > 1) and (None in channels):
fatal_error("Cannot use empty channel name when using multiple channels")
server = HTTPServer(("", PORT), ChannelHandler)
print("Server started at localhost:" + str(PORT))
try:
server.serve_forever()
except Exception:
# Catch all sorts of interrupts
print("Shutting server down")
server.shutdown()
print("Server shut down")

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail -x
# Directory of this file
readonly __DIR__="$(cd "$(dirname "${BASH_SOURCE[0]:?}")" && pwd)"
# reposerver python script
readonly reposerver="${__DIR__}/reposerver.py"
# Conda mock repository
readonly repo="${__DIR__}/repo/"
# Default value "mamba" for executable under test
export TEST_MAMBA_EXE="${TEST_MAMBA_EXE:-micromamba}"
# Avoid externally configured .condarc file
unset CONDARC
# Set up a temporary space for Conda environment and packages
readonly test_dir="$(mktemp -d -t mamba-test-reposerver-XXXXXXXXXX)"
export CONDA_ENVS_DIRS="${test_dir}/envs"
export CONDA_PKGS_DIRS="${test_dir}/pkgs"
readonly this_pid="$$"
# On exit, kill all subprocess and cleanup test directory.
trap 'rm -rf "${test_dir}"; pkill -P ${this_pid} || true' EXIT
start_server() {
exec python "${reposerver}" -n mychannel -d "${repo}" "$@"
}
test_install() {
local tmp=$(mktemp -d)
local condarc="${tmp}/condarc"
"${TEST_MAMBA_EXE}" create -y -p "${tmp}/env1" --override-channels -c $1/mychannel test-package --json
cat > "${condarc}" <<EOF
override_channels: true
channels: [mychannel]
channel_alias: "$1"
EOF
cat "${condarc}" >&2
"${TEST_MAMBA_EXE}" create -y -p "${tmp}/env2" test-package --json --rc-file "${condarc}" >&2
}
start_server & PID=$!
test_install http://localhost:8000 test-package --json
kill -TERM $PID
start_server --auth basic --user user --password test & PID=$!
test_install http://user:test@localhost:8000 test-package --json
kill -TERM $PID
start_server --auth basic --user user@email.com --password test & PID=$!
test_install http://user%40email.com:test@localhost:8000 test-package --json
kill -TERM $PID
start_server --token xy-12345678-1234-1234-1234-123456789012 & PID=$!
test_install http://localhost:8000/t/xy-12345678-1234-1234-1234-123456789012 test-package --json
kill -TERM $PID
if [[ "$(uname -s)" == "Linux" ]]; then
export KEY1=$(gpg --fingerprint "MAMBA1")
export KEY2=$(gpg --fingerprint "MAMBA2")
start_server --auth none --sign & PID=$!
sleep 5s
kill -TERM $PID
fi
python "${reposerver}" -d "${repo}" --auth basic --user user --password test --port 8005 & PID=$!
python "${reposerver}" -d "${repo}" --auth basic --user user --password test --port 8006 & PID2=$!
python "${reposerver}" -d "${repo}" --auth basic --user user --password test --port 8007 & PID3=$!
"${TEST_MAMBA_EXE}" create -y -q -n "env-${RANDOM}" --override-channels -c http://user:test@localhost:8005/ -c http://user:test@localhost:8006/ -c http://user:test@localhost:8007/ test-package --json
kill -TERM $PID
kill -TERM $PID2
kill -TERM $PID3
readonly channel_a="${__DIR__}/channel_a/"
readonly channel_b="${__DIR__}/channel_b/"
python "${reposerver}" \
-d "${repo}" -n defaults --token private-token -- \
-d "${channel_a}" -n channel_a --user user@email.com --password test -- \
-d "${channel_b}" -n channel_b --auth none & PID=$!
"${TEST_MAMBA_EXE}" create -y -q -n "env-${RANDOM}" --override-channels -c http://localhost:8000/t/private-token/defaults test-package --json
"${TEST_MAMBA_EXE}" create -y -q -n "env-${RANDOM}" --override-channels -c http://user%40email.com:test@localhost:8000/channel_a _r-mutex --json
kill -TERM $PID

View File

@ -10,8 +10,8 @@ from .helpers import create as umamba_create
from .helpers import login, logout, random_string
here = Path(__file__).absolute()
pyserver = here.parent.parent.parent / "mamba" / "tests" / "reposerver.py"
base_channel_directory = here.parent.parent.parent / "mamba" / "tests"
pyserver = here.parent.parent / "test-server" / "reposerver.py"
base_channel_directory = here.parent.parent / "test-server"
channel_a_directory = base_channel_directory / "channel_a"
channel_b_directory = base_channel_directory / "channel_b"
channel_r_directory = base_channel_directory / "repo"