├── .ci_tests_disabled ├── .codecov.yml ├── .github ├── dependabot.yml └── workflows │ ├── nodejs.yml │ ├── pr-frontend.yaml │ └── pr-golang.yaml ├── .gitignore ├── ChangeLog ├── Dockerfile ├── MAINTAINERS ├── Makefile ├── README.md ├── account ├── account.go ├── account_test.go └── mock_account.go ├── bin ├── set-config ├── snap-run-admin └── snap-run-service ├── charm ├── .gitignore ├── Makefile ├── README ├── bundle.yaml.in ├── dependencies.txt ├── packages.txt └── serial-vault │ ├── config.yaml │ ├── layer.yaml │ ├── metadata.yaml │ ├── reactive │ └── serial-vault.py │ └── templates │ └── settings.yaml ├── cmd ├── factory │ └── main.go ├── serial-vault-admin │ └── main.go └── serial-vault │ └── main.go ├── config ├── config.go └── config_test.go ├── crypt ├── crypt_user.go ├── crypto.go └── crypto_test.go ├── datastore ├── accountadapter.go ├── accountmanager.go ├── database.go ├── database_pg.go ├── database_sqlite.go ├── dbkeypairoperator.go ├── dbkeypairoperator_test.go ├── keypair.go ├── keypairadapter.go ├── keypairmanager.go ├── keypairstatusadapter.go ├── keypairstatusmanager.go ├── keystore.go ├── keystore_test.go ├── mock_database.go ├── mock_keystore.go ├── modeladapter.go ├── modeladapter_test.go ├── modelassertmanager.go ├── modelmanager.go ├── noncemanager.go ├── noncemanager_test.go ├── openidmanager.go ├── openidnoncestore.go ├── settingsmanager.go ├── signinglogadapter.go ├── signinglogmanager.go ├── signinglogmanager_test.go ├── substoreadapter.go ├── substoremanager.go ├── testlogadapter.go ├── testlogmanager.go ├── tpm20command.go ├── tpm20command_test.go ├── tpm20keypairoperator.go ├── tpm20keypairoperator_test.go ├── tpm2manager.go ├── tpm2manager_test.go ├── useradapter.go ├── useradapter_test.go ├── usermanager.go ├── validator.go └── validator_test.go ├── debian ├── changelog ├── compat ├── control ├── copyright ├── gbp.conf ├── rules ├── serial-vault.service └── source │ └── format ├── dependencies-devel.txt ├── dependencies.txt ├── docker-compose ├── docker-compose.yml ├── docker-entrypoint.sh ├── env └── settings.yaml ├── docs ├── assets │ ├── Design.png │ ├── ListModels.png │ ├── NewModel.png │ ├── NewSigningKey.png │ ├── SerialVault.png │ ├── ServicesDesign.png │ └── SigningLog.png ├── config.md ├── design.md ├── docker.md ├── index.md ├── installation.md ├── metadata.yaml ├── reference │ ├── commands.md │ ├── rest-api.md │ └── rest-api │ │ ├── v1-request-id.md │ │ ├── v1-serial.md │ │ └── v1-version.md ├── report-bug.md └── snap.md ├── factory-serial-vault ├── README.md ├── bin │ ├── serviceinit.sh │ ├── snap-run-signing-service │ └── snap-run-sync └── src │ └── apache │ ├── bin │ ├── disable-https │ ├── enable-https │ ├── httpd-wrapper │ ├── renew-certs │ ├── restart-apache │ └── run-httpd │ ├── conf │ ├── httpd.conf │ ├── nossl.conf │ └── ssl.conf │ ├── html │ └── index.html │ └── utilities │ ├── apache-utilities │ └── https-utilities ├── go.mod ├── go.sum ├── keystore ├── TestDeviceKey.asc ├── TestKey.asc ├── empty_report.xml ├── example_report.xml ├── placeholder.txt └── private-keys-v1 │ └── UytTqTvREVhx0tSfYC6KkFHmLWllIIZbQ3NsEG7OARrWuaXSRJyey0vjIQkTEvMO ├── launchers ├── cache-accounts-cron-job ├── serial-vault └── serial-vault-admin ├── manage ├── account.go ├── account_test.go ├── accountcache.go ├── client.go ├── client_test.go ├── database.go ├── database_test.go ├── manage.go ├── manage_test.go ├── user.go ├── user_test.go ├── useradd.go ├── userdelete.go ├── userlist.go └── userupdate.go ├── mdlint.py ├── ols-vms.conf ├── publish-to-swift ├── random ├── random.go └── random_test.go ├── run-checks ├── run-tests.sh ├── serial-vault-snap ├── README.md └── snap │ ├── gui │ └── icon.png │ └── snapcraft.yaml ├── service ├── account │ ├── actions.go │ ├── handlers_accounts.go │ ├── handlers_accounts_test.go │ ├── handlers_adminapi.go │ └── handlers_adminapi_test.go ├── app │ ├── handlers_app.go │ └── handlers_app_test.go ├── assertion │ ├── actions.go │ ├── actions_check.go │ ├── actions_user.go │ ├── handlers_adminapi.go │ ├── handlers_adminapi_test.go │ ├── handlers_api.go │ ├── handlers_api_test.go │ ├── handlers_user.go │ └── handlers_user_test.go ├── auth.go ├── auth │ ├── auth.go │ ├── auth_test.go │ └── middleware.go ├── core │ ├── handlers_core.go │ └── handlers_core_test.go ├── keypair │ ├── actions.go │ ├── actions_sync.go │ ├── handlers_adminapi.go │ ├── handlers_adminapi_test.go │ ├── handlers_keypairs.go │ └── handlers_keypairs_test.go ├── log │ └── log.go ├── metric │ ├── handler.go │ ├── handler_test.go │ ├── metrics.go │ ├── middleware.go │ └── middleware_test.go ├── middleware.go ├── model │ ├── actions.go │ ├── handlers_adminapi.go │ ├── handlers_adminapi_test.go │ ├── handlers_models.go │ └── handlers_models_test.go ├── pivot │ ├── handlers_pivot.go │ ├── handlers_pivot_test.go │ ├── handlers_pivot_user.go │ └── mock_data.go ├── request │ └── request.go ├── response │ ├── errors.go │ └── response.go ├── router.go ├── sentry │ ├── sentry.go │ └── sentry_test.go ├── sign │ ├── handlers_sign.go │ └── handlers_sign_test.go ├── signinglog │ ├── actions.go │ ├── actions_sync.go │ ├── handlers_adminapi.go │ ├── handlers_adminapi_test.go │ ├── handlers_signinglog.go │ └── handlers_signinglog_test.go ├── status │ ├── handler.go │ └── handler_test.go ├── store │ ├── actions.go │ ├── handlers_store.go │ └── handlers_store_test.go ├── substore │ ├── actions.go │ ├── handlers_adminapi.go │ ├── handlers_adminapi_test.go │ ├── handlers_substore.go │ └── handlers_substore_test.go ├── testlog │ ├── actions.go │ ├── actions_sync.go │ ├── handlers_adminapi.go │ ├── handlers_adminapi_test.go │ ├── handlers_testlog.go │ └── handlers_testlog_test.go └── user │ ├── actions.go │ ├── handlers_users.go │ └── handlers_users_test.go ├── settings.yaml.example ├── setup-container ├── snap ├── plugins │ └── x-apache.py └── snapcraft.yaml ├── spread.yaml ├── static ├── app.html ├── css │ ├── main.4a240a9d.chunk.css │ └── main.4a240a9d.chunk.css.map ├── font-awesome │ ├── css │ │ └── font-awesome.min.css │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── images │ ├── checkbox_checked_16.png │ ├── checkbox_unchecked_16.png │ ├── chevron-down.png │ ├── chevron-up.png │ ├── logo-ubuntu-black.svg │ ├── logo-ubuntu-white.svg │ ├── navigation-menu-plain.svg │ ├── serial-vault-logo.png │ └── serial-vault-logo.svg └── js │ ├── 2.e900bc10.chunk.js │ ├── 2.e900bc10.chunk.js.LICENSE.txt │ ├── 2.e900bc10.chunk.js.map │ ├── main.0f7d5b95.chunk.js │ ├── main.0f7d5b95.chunk.js.map │ ├── runtime-main.c685bd3b.js │ └── runtime-main.c685bd3b.js.map ├── store └── store.go ├── sync ├── client.go ├── client_test.go ├── cloud.go ├── database.go ├── start.go ├── start_test.go ├── sync.go └── sync_test.go ├── test └── authorized_keys ├── tests ├── lib │ ├── prepare-all.sh │ ├── prepare.sh │ ├── restore-each.sh │ ├── snap-names.sh │ └── utilities.sh └── main │ ├── documentation-builds │ └── task.yaml │ └── installation │ └── task.yaml ├── usso ├── constants.go ├── jwt.go ├── jwt_test.go ├── openid.go └── openid_test.go └── webapp-admin ├── .gitignore ├── README.md ├── build.sh ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── static │ ├── app.html │ ├── app_user.html │ └── images │ ├── checkbox_checked_16.png │ ├── checkbox_unchecked_16.png │ ├── chevron-down.png │ ├── chevron-up.png │ ├── logo-ubuntu-black.svg │ ├── logo-ubuntu-white.svg │ └── navigation-menu-plain.svg └── src ├── App.js ├── App.test.js ├── __tests__ ├── accountform-test.js ├── accountkeyform-test.js ├── accountlist-test.js ├── alertbox-test.js ├── dialogbox-test.js ├── footer-test.js ├── index-test.js ├── keypairadd-test.js ├── modeledit-test.js ├── modellist-test.js ├── navigation-test.js ├── pagination-test.js ├── signinglog-test.js ├── systemuser-test.js ├── useredit-test.js └── userlist-test.js ├── components ├── AccountEdit.js ├── AccountForm.js ├── AccountKeyForm.js ├── AccountList.js ├── AlertBox.js ├── Constants.js ├── DialogBox.js ├── Footer.js ├── Header.js ├── Index.js ├── Keypair.js ├── KeypairAdd.js ├── KeypairEdit.js ├── KeypairGenerate.js ├── KeypairList.js ├── KeypairStatus.js ├── KeypairStore.js ├── ModelAssertion.js ├── ModelEdit.js ├── ModelList.js ├── ModelRow.js ├── Navigation.js ├── NavigationSubmenu.js ├── NavigationUser.js ├── Pagination.js ├── SigningLog.js ├── SigningLogFilter.js ├── SigningLogRow.js ├── SubstoreForm.js ├── SubstoreList.js ├── SystemUserForm.js ├── UserEdit.js ├── UserList.js ├── UserRow.js ├── Utils.js └── messages.js ├── images ├── index.js ├── logo.svg ├── models ├── Ajax.js ├── accounts.js ├── assertions.js ├── keypairs.js ├── models.js ├── signinglog.js ├── users.js └── vault.js ├── registerServiceWorker.js └── sass └── App.scss /.ci_tests_disabled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/.ci_tests_disabled -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "mock_*.go" 3 | - "./**/mock_*.go" 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every weekday 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Automated javascript build CI 2 | on: 3 | push: 4 | paths: 'webapp-admin/**' 5 | branches: master 6 | jobs: 7 | build: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Use Node.js 12.x 12 | uses: actions/setup-node@v2.1.3 13 | with: 14 | node-version: 12.x 15 | - name: Build frontend 16 | run: | 17 | make build-frontend 18 | - name: Create Pull Request 19 | id: cpr 20 | uses: peter-evans/create-pull-request@v3 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | commit-message: Added js/css files 24 | title: 'Automated javascript build' 25 | -------------------------------------------------------------------------------- /.github/workflows/pr-frontend.yaml: -------------------------------------------------------------------------------- 1 | name: Javascript 2 | on: 3 | pull_request: 4 | paths: 'webapp-admin/**' 5 | jobs: 6 | build: 7 | name: Test 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Use Node.js 12.x 12 | uses: actions/setup-node@v2.1.3 13 | with: 14 | node-version: 12.x 15 | - name: Test frontend 16 | run: make test-frontend-ci 17 | -------------------------------------------------------------------------------- /.github/workflows/pr-golang.yaml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [pull_request] 3 | jobs: 4 | build: 5 | name: Test 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - name: Set up Go 9 | uses: actions/setup-go@v1 10 | with: 11 | go-version: 1.22.5 12 | id: go 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@v2 15 | - name: Setup 16 | run: make bootstrap 17 | - name: Test build 18 | run: make install 19 | - name: Run unit tests 20 | run: make unit-test 21 | lint: 22 | name: Lint 23 | runs-on: ubuntu-20.04 24 | steps: 25 | - name: Set up Go 26 | uses: actions/setup-go@v1 27 | with: 28 | go-version: 1.22.5 29 | id: go 30 | - name: Check out code into the Go module directory 31 | uses: actions/checkout@v2 32 | - name: Setup 33 | run: make bootstrap 34 | - name: Run static tests 35 | run: make static-test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .coverage 3 | *.log 4 | node_modules 5 | keystore/model* 6 | keystore/.primary* 7 | keystore/.context* 8 | keystore/.name* 9 | keystore/.prv* 10 | keystore/.pub* 11 | tags 12 | .tags 13 | coverage/ 14 | parts 15 | prime 16 | stage 17 | *.snap 18 | *.tar.bz2 19 | snap/.snapcraft 20 | .tests-extras 21 | __pycache__/ 22 | files/ 23 | /settings.yaml 24 | /vendor 25 | /bin 26 | /tmp 27 | /env 28 | /dist 29 | /tmp_charm 30 | .vscode/ 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-client golang-go ca-certificates 4 | ADD . /go/src/github.com/CanonicalLtd/serial-vault 5 | 6 | WORKDIR /go/src/github.com/CanonicalLtd/serial-vault 7 | # get dependencies 8 | RUN go get ./... 9 | 10 | COPY ./docker-compose/settings.yaml /go/src/github.com/CanonicalLtd/serial-vault 11 | COPY ./docker-compose/docker-entrypoint.sh / 12 | ENTRYPOINT ["/docker-entrypoint.sh"] 13 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Roberto Mier Escandon 2 | James Jesudason -------------------------------------------------------------------------------- /bin/set-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | err () { 4 | echo "$1" >&2 5 | } 6 | 7 | if [ -z "$SNAP_DATA" ]; then 8 | err "SNAP_DATA not set." 9 | exit 1 10 | fi 11 | 12 | if [ ! -w "$SNAP_DATA" ]; then 13 | err "Please re-run as root:" 14 | err "\`cat settings.yaml | sudo /snap/bin/serial-vault.config\`" 15 | exit 1 16 | fi 17 | 18 | if [ -t 0 ]; then 19 | err "This tool lets you replace the Serial-Vault configuration file:" 20 | err "\`cat settings.yaml | sudo /snap/bin/serial-vault.config\`" 21 | err "" 22 | err "You will need to restart the service to pick up any changes:" 23 | err "\`systemctl restart snap.serial-vault.serial-vault.service\`" 24 | err "" 25 | exit 1 26 | else 27 | cat - > "$SNAP_DATA/settings.yaml" 28 | fi 29 | -------------------------------------------------------------------------------- /bin/snap-run-admin: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$SNAP" 4 | 5 | bin/serial-vault-admin --config="$SNAP_DATA"/settings.yaml "$@" 6 | -------------------------------------------------------------------------------- /bin/snap-run-service: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$SNAP" 4 | 5 | bin/serial-vault-admin database --config="$SNAP_DATA"/settings.yaml 6 | #TODO It is needed to modify serial-vault params to be set in same way as admin tool. 7 | #That would set coherence in both services using -- or - for long and short params. 8 | bin/serial-vault -config="$SNAP_DATA"/settings.yaml 9 | -------------------------------------------------------------------------------- /charm/.gitignore: -------------------------------------------------------------------------------- 1 | bundle.yaml 2 | dist 3 | tmp 4 | -------------------------------------------------------------------------------- /charm/README: -------------------------------------------------------------------------------- 1 | This is the juju charm for this service 2 | 3 | Assuming your service is called myservice, the layer should look like this: 4 | 5 | charm 6 | ├── config.yaml # juju service config 7 | ├── dependencies.txt # source dependencies to build it 8 | ├── deploy-dev.yaml # juju deployer config for local test deployments 9 | ├── layer.yaml # charm layer configuration 10 | ├── Makefile 11 | ├── metadata.yaml # charm definition 12 | ├── reactive 13 | │   └── myservice.py # the meat of your charm 14 | └── templates 15 | └── myservice.ext # your service's configuration template 16 | 17 | 18 | There should be one main function in your myservice.py, that mainly just deals with your service. 19 | It should: 20 | 21 | 1. wait until all required relations/states have been reached (using @when and family) 22 | 2. gather all needed data from config/relations/elsewhere 23 | 3. render your config file template with this data 24 | 4. communicate with any custom relations 25 | 5. finally, set the service.configured state, to signal other layers to restart, etc. 26 | 27 | Most other common tasks (e.g. postrgres, logging, etc) are taked care of by 28 | various ols layers, and usually only need configuring in layer.yaml rather than 29 | any custom code. 30 | 31 | How to: 32 | 33 | - expose new service config: 34 | - edit config.yaml 35 | - use that config in your template or your charm code 36 | 37 | - add a new relation or layer: 38 | - add any relevant interfaces/layers to layer.yaml and dependencies.txt 39 | - add code to handle relation.available in reactive/ 40 | - if needed, add relation to metadata.py 41 | 42 | - add a custom action 43 | - add an actions.yaml describing your action 44 | - add an actions subdir, and implement you action in there 45 | -------------------------------------------------------------------------------- /charm/bundle.yaml.in: -------------------------------------------------------------------------------- 1 | series: xenial 2 | description: Serial-Vault development bundle 3 | applications: 4 | serial-vault-admin: 5 | num_units: 1 6 | charm: ../dist/xenial/serial-vault 7 | options: 8 | # ols layer config 9 | build_label: ${BUILD_LABEL} 10 | swift_username: ${OS_USERNAME} 11 | swift_password: ${OS_PASSWORD} 12 | swift_auth_url: ${OS_AUTH_URL} 13 | swift_region_name: ${OS_REGION_NAME} 14 | swift_tenant_name: ${OS_TENANT_NAME} 15 | swift_container_name: ${BUILD_CONTAINER_NAME} 16 | # service config 17 | mode: admin 18 | portAdmin: "8021" 19 | keystoreSecret: TEST_KEYSTORE_SECRET 20 | csrfAuthKey: "TEST_G5zRPZmeq3GhBGipRer1gtdC_32" 21 | enableUserAuth: false 22 | serial-vault-signing: 23 | num_units: 1 24 | charm: ../dist/xenial/serial-vault 25 | options: 26 | # ols layer config 27 | build_label: ${BUILD_LABEL} 28 | swift_username: ${OS_USERNAME} 29 | swift_password: ${OS_PASSWORD} 30 | swift_auth_url: ${OS_AUTH_URL} 31 | swift_region_name: ${OS_REGION_NAME} 32 | swift_tenant_name: ${OS_TENANT_NAME} 33 | swift_container_name: ${BUILD_CONTAINER_NAME} 34 | # service config 35 | mode: signing 36 | portSigning: "8021" 37 | keystoreSecret: TEST_KEYSTORE_SECRET 38 | postgresql: 39 | charm: cs:postgresql 40 | num_units: 1 41 | relations: 42 | - ["postgresql:db", "serial-vault-admin:db"] 43 | - ["postgresql:db-admin", "serial-vault-admin:db-admin"] 44 | - ["postgresql:db", "serial-vault-signing:db"] 45 | - ["postgresql:db-admin", "serial-vault-signing:db-admin"] 46 | -------------------------------------------------------------------------------- /charm/dependencies.txt: -------------------------------------------------------------------------------- 1 | ols-layers git+ssh://git.launchpad.net/~ubuntuone-pqm-team/ols-charm-deps/+git/ols-layers;revno=123deb23 2 | charm-wheels git+ssh://git.launchpad.net/~ubuntuone-hackers/ols-charm-deps/+git/wheels;revno=555749f1 3 | 4 | -------------------------------------------------------------------------------- /charm/packages.txt: -------------------------------------------------------------------------------- 1 | python3-psycopg2 2 | libpq-dev 3 | virtualenv 4 | language-pack-en-base 5 | bzr 6 | python3-swiftclient 7 | gettext-base 8 | -------------------------------------------------------------------------------- /charm/serial-vault/layer.yaml: -------------------------------------------------------------------------------- 1 | repo: https://github.com/CanonicalLtd/serial-vault 2 | includes: 3 | - layer:ols-http 4 | - layer:ols-pg 5 | options: 6 | ols: 7 | service_name: serial-vault 8 | config_filename: settings.yaml 9 | user: ols 10 | tarball_payload: True 11 | ols-http: 12 | port: 8021 13 | active_flag_enabled: True 14 | ols-pg: 15 | databases: 16 | db: 17 | name: serial-vault 18 | roles: serial-vault 19 | -------------------------------------------------------------------------------- /charm/serial-vault/metadata.yaml: -------------------------------------------------------------------------------- 1 | name: serial-vault 2 | display-name: serial-vault 3 | summary: Serial Vault Service 4 | maintainers: 5 | - Online Services 6 | description: "A Go web service that digitally signs device assertion details." 7 | subordinate: false 8 | series: 9 | - xenial 10 | provides: 11 | serial-vault: 12 | interface: http 13 | nrpe-external-master: 14 | interface: nrpe-external-master 15 | scope: container 16 | requires: 17 | db: 18 | interface: pgsql 19 | resources: 20 | serial-vault: 21 | type: file 22 | filename: serial-vault.tar.gz 23 | description: Serial Vault resources 24 | -------------------------------------------------------------------------------- /charm/serial-vault/reactive/serial-vault.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | import ols.base 4 | import ols.render 5 | import ols.postgres 6 | import ols.http 7 | 8 | from charms.reactive import ( 9 | when, 10 | when_not, 11 | set_state, 12 | remove_state, 13 | ) 14 | 15 | from charmhelpers.core import hookenv 16 | 17 | @when('ols.configured', 'db.master.available') 18 | def configure(pgsql): 19 | if hookenv.hook_name() == 'update-status': 20 | return 21 | 22 | config = hookenv.config().copy() 23 | maindb, standbys = ols.postgres.get_db_uris(pgsql) 24 | config['maindb'] = maindb 25 | if standbys: 26 | config['standbys_urls'] = standbys 27 | 28 | name = ols.base.service_name() 29 | working_dir = ols.base.code_dir() 30 | config_path = ols.base.service_config_path() 31 | 32 | changed = ols.render.setup_charm_service( 33 | exec_path='{}/serial-vault'.format(working_dir), 34 | exec_args='--config {}'.format(config_path), 35 | ) 36 | 37 | changed |= ols.render.render( 38 | template='settings.yaml', 39 | target=config_path, 40 | context=config, 41 | ) 42 | 43 | if not changed: 44 | hookenv.log('nothing changed, doing nothing') 45 | # other layers set status to waiting, so we need to unset if we're good 46 | hookenv.status_set('active', 'ready') 47 | return 48 | 49 | restart_cmd = ['systemctl', 'restart', name] 50 | try: 51 | subprocess.check_output(restart_cmd) 52 | except subprocess.CalledProcessError as exc: 53 | if exc.output: 54 | hookenv.log(exc.output) 55 | hookenv.status_set('blocked', 'error restarting service') 56 | raise 57 | else: 58 | set_state('service.configured') 59 | hookenv.status_set('active', 'ready') 60 | -------------------------------------------------------------------------------- /charm/serial-vault/templates/settings.yaml: -------------------------------------------------------------------------------- 1 | title: "{{ title }}" 2 | logo: "{{ logo }}" 3 | docRoot: "{{ docRoot }}" 4 | mode: "{{ mode }}" 5 | portAdmin: "{{ portAdmin }}" 6 | portSigning: "{{ portSigning }}" 7 | driver: "{{ dbdriver }}" 8 | datasource: "{{ maindb }}" 9 | keystore: "{{ keystore }}" 10 | keystoreSecret: "{{ keystoreSecret }}" 11 | csrfAuthKey: "{{ csrfAuthKey }}" 12 | urlHost: "{{ urlHost }}" 13 | urlScheme: "{{ urlScheme }}" 14 | enableUserAuth: {{ enableUserAuth }} 15 | jwtSecret: "{{ jwtSecret }}" 16 | syncUrl: "{{ syncUrl }}" 17 | syncUser: "{{ syncUser }}" 18 | syncAPIKey: "{{ syncAPIKey }}" 19 | sentryDSN: "{{ sentryDSN }}" 20 | -------------------------------------------------------------------------------- /cmd/factory/main.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | 27 | "github.com/CanonicalLtd/serial-vault/datastore" 28 | "github.com/CanonicalLtd/serial-vault/sync" 29 | "github.com/jessevdk/go-flags" 30 | ) 31 | 32 | func main() { 33 | datastore.Environ = &datastore.Env{} 34 | 35 | err := run() 36 | if err != nil { 37 | os.Exit(1) 38 | } 39 | 40 | } 41 | 42 | func run() error { 43 | // Parse the command line arguments and execute the command 44 | parser := flags.NewParser(&sync.Sync, flags.HelpFlag) 45 | _, err := parser.Parse() 46 | 47 | if err != nil { 48 | if e, ok := err.(*flags.Error); ok { 49 | if e.Type == flags.ErrHelp || e.Type == flags.ErrCommandRequired { 50 | parser.WriteHelp(os.Stdout) 51 | return nil 52 | } 53 | } 54 | fmt.Println(err) 55 | } 56 | 57 | return err 58 | } 59 | -------------------------------------------------------------------------------- /cmd/serial-vault-admin/main.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | 26 | "github.com/CanonicalLtd/serial-vault/datastore" 27 | "github.com/CanonicalLtd/serial-vault/manage" 28 | "github.com/jessevdk/go-flags" 29 | ) 30 | 31 | func main() { 32 | datastore.Environ = &datastore.Env{} 33 | 34 | err := run() 35 | if err != nil { 36 | os.Exit(1) 37 | } 38 | 39 | } 40 | 41 | func run() error { 42 | // Parse the command line arguments and execute the command 43 | parser := flags.NewParser(&manage.Manage, flags.HelpFlag) 44 | _, err := parser.Parse() 45 | 46 | if err != nil { 47 | if e, ok := err.(*flags.Error); ok { 48 | if e.Type == flags.ErrHelp || e.Type == flags.ErrCommandRequired { 49 | parser.WriteHelp(os.Stdout) 50 | return nil 51 | } 52 | } 53 | fmt.Println(err) 54 | } 55 | 56 | return err 57 | } 58 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package config 21 | 22 | import "testing" 23 | 24 | func TestReadConfig(t *testing.T) { 25 | settings := Settings{} 26 | err := ReadConfig(&settings, "../settings.yaml.example") 27 | if err != nil { 28 | t.Errorf("Error reading config file: %v", err) 29 | } 30 | } 31 | 32 | func TestReadConfigInvalidPath(t *testing.T) { 33 | settings := Settings{} 34 | err := ReadConfig(&settings, "not a good path") 35 | if err == nil { 36 | t.Error("Expected an error with an invalid config file.") 37 | } 38 | } 39 | 40 | func TestReadConfigInvalidFile(t *testing.T) { 41 | settings := Settings{} 42 | err := ReadConfig(&settings, "../README.md") 43 | if err == nil { 44 | t.Error("Expected an error with an invalid config file.") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crypt/crypt_user.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package crypt 21 | 22 | // #cgo LDFLAGS: -lcrypt 23 | // #define _GNU_SOURCE 24 | // #include 25 | // #include 26 | import "C" 27 | import "unsafe" 28 | 29 | // CLibCryptUser wraps C library crypt_r 30 | func CLibCryptUser(key, salt string) string { 31 | data := C.struct_crypt_data{} 32 | ckey := C.CString(key) 33 | csalt := C.CString(salt) 34 | out := C.GoString(C.crypt_r(ckey, csalt, &data)) 35 | C.free(unsafe.Pointer(ckey)) 36 | C.free(unsafe.Pointer(csalt)) 37 | return out 38 | } 39 | -------------------------------------------------------------------------------- /datastore/database_pg.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package datastore 22 | 23 | import ( 24 | "database/sql" 25 | 26 | "github.com/CanonicalLtd/serial-vault/service/log" 27 | _ "github.com/lib/pq" // postgresql driver 28 | ) 29 | 30 | // openPostgreSQLDatabase return an open database connection for an postgresql database 31 | func openPostgreSQLDatabase(driver, dataSource string) { 32 | // Open the database connection 33 | db, err := sql.Open(driver, dataSource) 34 | if err != nil { 35 | log.Fatalf("Error opening the database: %v", err) 36 | } 37 | 38 | // Check that we have a valid database connection 39 | err = db.Ping() 40 | if err != nil { 41 | log.Fatalf("Error accessing the database: %v", err) 42 | } 43 | 44 | Environ.DB = &DB{db} 45 | OpenidNonceStore.DB = &DB{db} 46 | } 47 | -------------------------------------------------------------------------------- /datastore/database_sqlite.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package datastore 22 | 23 | import ( 24 | "database/sql" 25 | 26 | "github.com/CanonicalLtd/serial-vault/service/log" 27 | 28 | _ "github.com/mattn/go-sqlite3" // sqlite driver 29 | ) 30 | 31 | // openSQLiteDatabase return an open database connection for an sqlite database 32 | func openSQLiteDatabase(driver, dataSource string) { 33 | // Open the database connection 34 | db, err := sql.Open(driver, dataSource) 35 | if err != nil { 36 | log.Fatalf("Error opening the database: %v\n", err) 37 | } 38 | 39 | // Check that we have a valid database connection 40 | err = db.Ping() 41 | if err != nil { 42 | log.Fatalf("Error accessing the database: %v\n", err) 43 | } 44 | 45 | Environ.DB = &DB{db} 46 | OpenidNonceStore.DB = &DB{db} 47 | } 48 | -------------------------------------------------------------------------------- /datastore/keypairstatusadapter.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | // ListAllowedKeypairStatus return the list of keypairs that are in progress 23 | func (db *DB) ListAllowedKeypairStatus(authorization User) ([]KeypairStatus, error) { 24 | switch authorization.Role { 25 | case Invalid: // Authentication is disabled 26 | fallthrough 27 | case Superuser: 28 | return db.listAllKeypairStatus() 29 | case Admin: 30 | return db.listKeypairStatusFilteredByUser(authorization.Username) 31 | default: 32 | return []KeypairStatus{}, nil 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /datastore/keystore_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "testing" 24 | 25 | "github.com/CanonicalLtd/serial-vault/config" 26 | ) 27 | 28 | func TestGetKeyStoreFilesystem(t *testing.T) { 29 | // Set up the environment variables 30 | config := config.Settings{KeyStoreType: "filesystem", KeyStorePath: "../keystore"} 31 | Environ = &Env{Config: config} 32 | 33 | err := OpenKeyStore(config) 34 | if err != nil { 35 | t.Error("Error setting up the filesystem keystore") 36 | } 37 | } 38 | 39 | func TestGetKeyStoreInvalid(t *testing.T) { 40 | // Set up the environment variables 41 | config := config.Settings{KeyStoreType: "invalid", KeyStorePath: "../keystore"} 42 | Environ = &Env{Config: config} 43 | 44 | err := OpenKeyStore(config) 45 | if err == nil { 46 | t.Errorf("Expected error, but got success: %v", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /datastore/mock_keystore.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "errors" 24 | 25 | "github.com/CanonicalLtd/serial-vault/config" 26 | "github.com/snapcore/snapd/asserts" 27 | ) 28 | 29 | type errorMockKeypairManager struct{} 30 | 31 | // GetMemoryKeyStore creates a mocked keystore 32 | func GetMemoryKeyStore(config config.Settings) (*KeypairDatabase, error) { 33 | db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 34 | KeypairManager: asserts.NewMemoryKeypairManager(), 35 | }) 36 | kdb := KeypairDatabase{FilesystemStore, db, nil} 37 | return &kdb, err 38 | } 39 | 40 | func (emkdb *errorMockKeypairManager) Get(keyID string) (asserts.PrivateKey, error) { 41 | return nil, errors.New("MOCK error fetching the private key") 42 | } 43 | 44 | func (emkdb *errorMockKeypairManager) Put(privKey asserts.PrivateKey) error { 45 | return errors.New("MOCK error saving the private key") 46 | } 47 | 48 | func (emkdb *errorMockKeypairManager) Delete(keyID string) error { 49 | return errors.New("MOCK error deleting the private key") 50 | } 51 | 52 | // GetErrorMockKeyStore creates a mocked keystore 53 | func GetErrorMockKeyStore(config config.Settings) (*KeypairDatabase, error) { 54 | mockStore := new(errorMockKeypairManager) 55 | 56 | db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 57 | KeypairManager: mockStore, 58 | }) 59 | kdb := KeypairDatabase{FilesystemStore, db, nil} 60 | return &kdb, err 61 | } 62 | -------------------------------------------------------------------------------- /datastore/modeladapter_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "testing" 24 | ) 25 | 26 | func TestModelName(t *testing.T) { 27 | name := "my-model-01" 28 | err := validateModelName(name) 29 | if err != nil { 30 | t.Errorf("Not a valid model name: %v", err) 31 | } 32 | } 33 | 34 | func TestModelNameEmpty(t *testing.T) { 35 | name := "" 36 | err := validateModelName(name) 37 | if err == nil { 38 | t.Error("Expected name not to be valid, but it is") 39 | } 40 | if err.Error() != "Model name must not be empty" { 41 | t.Error("Error happening is not the one searched for") 42 | } 43 | } 44 | 45 | func TestModelNameUpperChar(t *testing.T) { 46 | name := "my-Model-01" 47 | err := validateModelName(name) 48 | if err == nil { 49 | t.Error("Expected name not to be valid, but it is") 50 | } 51 | if err.Error() != "Model name must not contain uppercase characters" { 52 | t.Error("Error happening is not the one searched for") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /datastore/noncemanager_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import "testing" 23 | 24 | func TestNonceGeneration(t *testing.T) { 25 | // Generate some nonces 26 | var nonces [10]DeviceNonce 27 | for i := 0; i < 10; i++ { 28 | nonce, err := generateNonce() 29 | if err != nil { 30 | t.Error("Error generating nonce") 31 | } 32 | nonces[i] = nonce 33 | } 34 | 35 | // Check that the nonces look unique and valid 36 | for i := 1; i < 10; i++ { 37 | thisOne := nonces[i] 38 | lastOne := nonces[i-1] 39 | 40 | if thisOne.Nonce == lastOne.Nonce { 41 | t.Error("Generated nonces are not unique") 42 | } 43 | if lastOne.TimeStamp > thisOne.TimeStamp { 44 | t.Error("Nonce based on invalid timestamp") 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /datastore/signinglogadapter.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | // ListAllowedSigningLog return signing logs the user is authorized to see 23 | func (db *DB) ListAllowedSigningLog(authorization User) ([]SigningLog, error) { 24 | switch authorization.Role { 25 | case Invalid: // Authentication disabled 26 | fallthrough 27 | case Superuser: 28 | return db.listAllSigningLog() 29 | case SyncUser: 30 | fallthrough 31 | case Admin: 32 | return db.listSigningLogFilteredByUser(authorization.Username) 33 | default: 34 | return []SigningLog{}, nil 35 | } 36 | } 37 | 38 | // ListAllowedSigningLogForAccount return signing logs the user is authorized to see 39 | func (db *DB) ListAllowedSigningLogForAccount(authorization User, authorityID string, params *SigningLogParams) ([]SigningLog, error) { 40 | switch authorization.Role { 41 | case Invalid: // Authentication disabled 42 | fallthrough 43 | case Superuser: 44 | return db.listAllSigningLogForAccount(authorityID, params) 45 | case SyncUser: 46 | fallthrough 47 | case Admin: 48 | return db.listSigningLogForAccountFilteredByUser(authorization.Username, authorityID, params) 49 | default: 50 | return []SigningLog{}, nil 51 | } 52 | } 53 | 54 | // AllowedSigningLogFilterValues return signing log filters authorized for the user 55 | func (db *DB) AllowedSigningLogFilterValues(authorization User, authorityID string) (SigningLogFilters, error) { 56 | switch authorization.Role { 57 | case Invalid: // Authentication disabled 58 | fallthrough 59 | case Superuser: 60 | return db.allSigningLogFilterValues(authorityID) 61 | case Admin: 62 | return db.signingLogFilterValuesFilteredByUser(authorization.Username, authorityID) 63 | default: 64 | return SigningLogFilters{}, nil 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /datastore/testlogadapter.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package datastore 22 | 23 | import "errors" 24 | 25 | // ListAllowedTestLog return test logs the user is authorized to see 26 | func (db *DB) ListAllowedTestLog(authorization User) ([]TestLog, error) { 27 | switch authorization.Role { 28 | case Invalid: // Authentication disabled 29 | fallthrough 30 | case Superuser: 31 | return db.listAllTestLog() 32 | case SyncUser: 33 | fallthrough 34 | case Admin: 35 | return db.listTestLogFilteredByUser(authorization.Username) 36 | default: 37 | return []TestLog{}, nil 38 | } 39 | } 40 | 41 | // SyncListTestLogs fetches the test logs from the factory database 42 | func (db *DB) SyncListTestLogs() ([]TestLog, error) { 43 | if Environ.Config.Driver != "sqlite3" { 44 | return nil, errors.New("Only valid within a factory") 45 | } 46 | 47 | return db.listAllTestLog() 48 | } 49 | 50 | // UpdateAllowedTestLog marks a test log as synced 51 | func (db *DB) UpdateAllowedTestLog(ID int, authorization User) error { 52 | switch authorization.Role { 53 | case Superuser: 54 | fallthrough 55 | case SyncUser: 56 | fallthrough 57 | case Admin: 58 | _, err := db.Exec(updateTestLogSyncedSQL, ID, authorization.Username) 59 | return err 60 | default: 61 | return errors.New("Not authorized to update a testlog") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /datastore/tpm20command.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "os/exec" 24 | 25 | "github.com/CanonicalLtd/serial-vault/service/log" 26 | ) 27 | 28 | // TPM20Command is an interface for wrapping the TPM2.0 shell commands 29 | type TPM20Command interface { 30 | runCommand(command string, args ...string) error 31 | } 32 | 33 | type tpm20Command struct{} 34 | 35 | func (tcmd *tpm20Command) runCommand(command string, args ...string) error { 36 | cmd := exec.Command(command, args...) 37 | out, err := cmd.Output() 38 | if err != nil { 39 | log.Printf("Error in TPM %s, %v", command, err) 40 | log.Println(string(out[:])) 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /datastore/tpm20command_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "io/ioutil" 24 | "testing" 25 | 26 | "github.com/CanonicalLtd/serial-vault/service/log" 27 | ) 28 | 29 | type mockTPM20Command struct{} 30 | 31 | func TestRunCommand(t *testing.T) { 32 | command := tpm20Command{} 33 | err := command.runCommand("ls", "-l") 34 | if err != nil { 35 | t.Errorf("Error running shell command: %v", err) 36 | } 37 | } 38 | 39 | func TestRunCommandBadCommand(t *testing.T) { 40 | command := tpm20Command{} 41 | 42 | err := command.runCommand("thisreallyshouldnotwork", "-l") 43 | if err == nil { 44 | t.Error("Expected error, got success") 45 | } 46 | } 47 | 48 | func (tcmd *mockTPM20Command) runCommand(command string, args ...string) error { 49 | log.Printf(" Mock command: %s\n", command) 50 | if command == "tpm2_hmac" { 51 | filename := args[len(args)-1] 52 | err := ioutil.WriteFile(filename, []byte("fake-hmac-ed-data"), 0600) 53 | return err 54 | } 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /datastore/tpm2manager.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "io/ioutil" 24 | 25 | "github.com/CanonicalLtd/serial-vault/service/log" 26 | ) 27 | 28 | // TPM2InitializeKeystore initializes the TPM 2.0 module by taking ownership of the module 29 | // and generating the key for sealing and unsealing signing keys. The context values provide 30 | // the TPM 2.0 authentication and these are stored in the database. 31 | // Main TPM 2.0 operations: 32 | // - takeownership 33 | // - createprimary 34 | func TPM2InitializeKeystore(command TPM20Command) error { 35 | log.Println("Initialize the TPM Keystore...") 36 | 37 | // Generate a unique file name to hold the primary key context 38 | primaryKeyContext, err := ioutil.TempFile(Environ.Config.KeyStorePath, ".primary") 39 | if err != nil { 40 | return err 41 | } 42 | 43 | if command == nil { 44 | command = &tpm20Command{} 45 | } 46 | 47 | // Take ownership of the TPM 2.0 module 48 | err = command.runCommand("tpm2_takeownership", "-c") 49 | if err != nil { 50 | log.Printf("Error in TPM takeownership, %v", err) 51 | return err 52 | } 53 | 54 | // Create the primary key in the hierarchy 55 | err = command.runCommand("tpm2_createprimary", "-A", "o", "-g", algSHA256, "-G", algRSA, "-C", primaryKeyContext.Name()) 56 | if err != nil { 57 | log.Printf("Error in TPM createprimary, %v", err) 58 | return err 59 | } 60 | 61 | // Save the primary key context filepath in the database 62 | err = Environ.DB.PutSetting(Setting{Code: "parent", Data: primaryKeyContext.Name()}) 63 | if err != nil { 64 | log.Printf("Error in saving the parent key path in settings, %v", err) 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /datastore/tpm2manager_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package datastore 21 | 22 | import ( 23 | "testing" 24 | 25 | "github.com/CanonicalLtd/serial-vault/config" 26 | ) 27 | 28 | func TestTPM2InitializeKeystore(t *testing.T) { 29 | // Set up the environment variables 30 | config := config.Settings{KeyStorePath: "../keystore", KeyStoreType: "tpm2.0", KeyStoreSecret: "this needs to be 32 bytes long!!"} 31 | Environ = &Env{Config: config, DB: &MockDB{}} 32 | 33 | err := TPM2InitializeKeystore(&mockTPM20Command{}) 34 | if err != nil { 35 | t.Errorf("Error initializing the TPM keystore: %v", err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | serial-vault (2.1-1ubuntu1) xenial; urgency=medium 2 | 3 | * Updating documentation 4 | 5 | -- James Jesudason Tue, 27 Feb 2018 16:22:05 +0000 6 | 7 | serial-vault (2.1-0ubuntu3) xenial; urgency=medium 8 | 9 | * Testing govendor 10 | 11 | -- Roberto Mier Escandon Fri, 23 Feb 2018 18:38:14 +0100 12 | 13 | serial-vault (2.1-0) xenial; urgency=medium 14 | 15 | * Reseller features 16 | 17 | -- James Jesudason Fri, 23 Feb 2018 13:27:36 +0000 18 | 19 | serial-vault (2.0-1) xenial; urgency=medium 20 | 21 | * Multi tenant version 22 | 23 | -- Roberto Mier Escandon Mon, 18 Sep 2017 13:28:55 +0200 24 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: serial-vault 3 | Source: https://github.com/CanonicalLtd/serial-vault 4 | 5 | Files: * 6 | Copyright: 2016-2017 Canonical Ltd 7 | License: GPL-3 8 | This program is free software: you can redistribute it and/or modify it 9 | under the terms of the the GNU General Public License version 3, as 10 | published by the Free Software Foundation. 11 | . 12 | This program is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranties of 14 | MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR 15 | PURPOSE. See the applicable version of the GNU Lesser General Public 16 | License for more details. 17 | . 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see . 20 | . 21 | On Debian systems, the complete text of the GNU General Public License 22 | can be found in `/usr/share/common-licenses/GPL-3' -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | pristine-tar = True 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | PROJECT := serial-vault 4 | export DH_OPTIONS 5 | export DH_GOPKG := github.com/CanonicalLtd/${PROJECT} 6 | export GOPATH := ${CURDIR}/_build 7 | export GOBIN := ${GOPATH}/bin 8 | export PATH := ${GOBIN}:${PATH} 9 | BLDPATH := $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) 10 | SRCDIR := ${CURDIR}/_build/src/${DH_GOPKG} 11 | DESTDIR := ${CURDIR}/debian/${PROJECT} 12 | BINDIR := /usr/bin 13 | LIBDIR := /usr/lib/${PROJECT} 14 | CONFDIR := /etc/${PROJECT} 15 | ASSETSDIR := /usr/share/${PROJECT} 16 | CRONDIR := /etc/cron.d 17 | 18 | %: 19 | dh $@ --buildsystem=golang --with=golang --with=systemd 20 | 21 | override_dh_auto_build: 22 | mkdir -p ${SRCDIR} 23 | mkdir -p ${GOBIN} 24 | # copy project to local srcdir to build from there 25 | rsync -avz --progress --exclude=_build --exclude=obj-${BLDPATH} \ 26 | --exclude=debian --exclude=webapp-admin/node_modules \ 27 | --exclude=webapp-user/node_modules . $(SRCDIR) 28 | # build go code 29 | (cd ${SRCDIR} && go install -v ./...) 30 | 31 | override_dh_auto_test: 32 | (cd ${SRCDIR} && go test -v ./...) 33 | 34 | override_dh_auto_install: 35 | mkdir -p ${DESTDIR}/${BINDIR} 36 | mkdir -p ${DESTDIR}/${CRONDIR} 37 | mkdir -p ${DESTDIR}/${LIBDIR} 38 | mkdir -p ${DESTDIR}/${CONFDIR} 39 | mkdir -p ${DESTDIR}/${ASSETSDIR} 40 | cp ${CURDIR}/_build/bin/serial-vault* ${DESTDIR}/${LIBDIR} 41 | cp ${SRCDIR}/launchers/serial-vault* ${DESTDIR}/${BINDIR} 42 | cp ${SRCDIR}/launchers/cache-accounts-cron-job ${DESTDIR}/${CRONDIR} 43 | cp -r ${SRCDIR}/static ${DESTDIR}/${ASSETSDIR} 44 | cp ${SRCDIR}/settings.yaml ${DESTDIR}/${CONFDIR} 45 | cp ${SRCDIR}/keystore/TestDeviceKey.asc ${DESTDIR}/${CONFDIR} 46 | # update docRoot setting to point assets dir 47 | sed -i 's/^docRoot:.*/docRoot: \"\/usr\/share\/serial-vault\"/' ${DESTDIR}/${CONFDIR}/settings.yaml 48 | # configure launchers to be used in systemd service 49 | sed -i 's/{{[ ]*bindir[ ]*}}/\/usr\/lib\/serial-vault/g' ${DESTDIR}/${BINDIR}/serial-vault 50 | sed -i 's/{{[ ]*bindir[ ]*}}/\/usr\/lib\/serial-vault/g' ${DESTDIR}/${BINDIR}/serial-vault-admin 51 | sed -i 's/{{[ ]*confdir[ ]*}}/\/etc\/serial-vault/g' ${DESTDIR}/${BINDIR}/serial-vault 52 | sed -i 's/{{[ ]*confdir[ ]*}}/\/etc\/serial-vault/g' ${DESTDIR}/${BINDIR}/serial-vault-admin 53 | chmod a+x ${DESTDIR}/${BINDIR}/serial-vault* 54 | 55 | override_dh_auto_clean: 56 | dh_clean 57 | rm -rf ${CURDIR}/obj-${BLDPATH} 58 | rm -rf ${CURDIR}/_build 59 | -------------------------------------------------------------------------------- /debian/serial-vault.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Service for serial-vault 3 | Wants=network-online.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/serial-vault -config=/etc/serial-vault/settings.yaml 7 | SyslogIdentifier=serial-vault 8 | Restart=on-failure 9 | 10 | TimeoutStopSec=30 11 | Type=simple 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /dependencies-devel.txt: -------------------------------------------------------------------------------- 1 | build-essential 2 | git 3 | -------------------------------------------------------------------------------- /dependencies.txt: -------------------------------------------------------------------------------- 1 | postgresql 2 | language-pack-en 3 | -------------------------------------------------------------------------------- /docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: postgres 5 | env_file: 6 | - ./env 7 | web: 8 | build: .. 9 | depends_on: 10 | - db 11 | env_file: 12 | - ./env 13 | ports: 14 | - "8080:8080" 15 | - "8081:8081" 16 | -------------------------------------------------------------------------------- /docker-compose/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -u 2 | 3 | set +e 4 | 5 | # Polling PostgreSQL server 6 | while true ; do 7 | result=$(psql -lqt postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT | cut -d \| -f 1 | grep -qw $POSTGRES_DB) 8 | ret=$? 9 | if [ $ret -ne 0 ]; then 10 | echo "Waiting database creation on PostgreSQL server." 11 | sleep 3 12 | else 13 | break 14 | fi 15 | done 16 | 17 | set -e 18 | 19 | cd /go/src/github.com/CanonicalLtd/serial-vault 20 | 21 | sed -i \ 22 | -e "s/API_KEY/$API_KEY/g" \ 23 | -e "s/POSTGRES_HOST/$POSTGRES_HOST/g" \ 24 | -e "s/POSTGRES_DB/$POSTGRES_DB/g" \ 25 | -e "s/POSTGRES_PASSWORD/$POSTGRES_PASSWORD/g" \ 26 | -e "s/POSTGRES_PORT/$POSTGRES_PORT/g" \ 27 | -e "s/POSTGRES_USER/$POSTGRES_USER/g" \ 28 | -e "s/KEYSTORE_SECRET/$KEYSTORE_SECRET/g" \ 29 | settings.yaml 30 | 31 | go run cmd/serial-vault/main.go -mode=admin & 32 | go run cmd/serial-vault/main.go -mode=signing 33 | -------------------------------------------------------------------------------- /docker-compose/env: -------------------------------------------------------------------------------- 1 | API_KEY=U2VyaWFsIFZhdWx0Cg 2 | KEYSTORE_SECRET=Some Secret 3 | POSTGRES_DB=identityvault 4 | POSTGRES_HOST=db 5 | POSTGRES_PASSWORD=ubuntu 6 | POSTGRES_PORT=5432 7 | POSTGRES_USER=identityvault 8 | -------------------------------------------------------------------------------- /docker-compose/settings.yaml: -------------------------------------------------------------------------------- 1 | title: "Serial Vault" 2 | logo: "/static/images/logo-ubuntu-white.svg" 3 | 4 | # Path to the assets (${docRoot}/static) 5 | docRoot: "." 6 | 7 | # Backend database details 8 | driver: "postgres" 9 | datasource: "postgres://POSTGRES_USER:POSTGRES_PASSWORD@POSTGRES_HOST:POSTGRES_PORT/POSTGRES_DB?sslmode=disable" 10 | 11 | # Signing Key Store 12 | # keystore: "filesystem" 13 | # keystorePath: "./keystore" 14 | 15 | # For Database 16 | keystore: "database" 17 | keystoreSecret: "KEYSTORE_SECRET" 18 | 19 | # For TPM 2.0 20 | #keystore: "tpm2.0" 21 | #keystorePath: "./keystore" 22 | #keystoreSecret: "this needs to be 32 bytes long!!" 23 | 24 | # Valid API keys 25 | apiKeys: 26 | - API_KEY 27 | 28 | # 32 bytes long key to protect server from cross site request forgery attacks 29 | csrfAuthKey: "32_BYTES_LONG_CSRF_AUTH_KEY" 30 | -------------------------------------------------------------------------------- /docs/assets/Design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/Design.png -------------------------------------------------------------------------------- /docs/assets/ListModels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/ListModels.png -------------------------------------------------------------------------------- /docs/assets/NewModel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/NewModel.png -------------------------------------------------------------------------------- /docs/assets/NewSigningKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/NewSigningKey.png -------------------------------------------------------------------------------- /docs/assets/SerialVault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/SerialVault.png -------------------------------------------------------------------------------- /docs/assets/ServicesDesign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/ServicesDesign.png -------------------------------------------------------------------------------- /docs/assets/SigningLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/docs/assets/SigningLog.png -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Serial Vault Design" 3 | table_of_contents: False 4 | --- 5 | 6 | # Overview 7 | 8 | ![Serial Vault Design](assets/Design.png) 9 | 10 | The SerialVault is comprised of three services, deployed at a data centre: 11 | 12 | * Signing Service 13 | * Admin Service 14 | 15 | The Signing Service is the publicly accessible service that is used to generate a 16 | signed serial assertion using a predefined signing key. The Admin Service is a private 17 | service, that is designed to be accessible only from the data centre, that allows 18 | signing keys and models to be defined. 19 | 20 | # Design 21 | 22 | ![Serial Vault Services Design](assets/ServicesDesign.png) 23 | 24 | The Signing Service provides two main methods: one to generate a nonce (/request-id) 25 | and the serial method (/serial). Before calling the sign method, a nonce will need to be 26 | generated and included in the serial-request assertion that is sent to the sign method. 27 | The sign method validates the nonce, checks that the model is a recognised one, generates 28 | a serial assertion and then signs and returns a serial assertion. 29 | 30 | The sign method accesses the Signing Key from the memory store. The first time a Signing 31 | Key is used, it will be retrieved from the database, decrypted and added to the memory store. 32 | 33 | The Admin Service allows signing keys to be uploaded, and for models to be defined. Whenever 34 | a valid sign request is made, the serial number and device-key fingerprint are stored in the 35 | database. The Admin Service provides a Signing Log view that shows the valid serial number 36 | and device-keys fingerprints that have been used. 37 | 38 | -------------------------------------------------------------------------------- /docs/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serial Vault Docker 3 | table_of_contents: False 4 | --- 5 | 6 | # Try with Docker 7 | 8 | There is also available in this project a configuration to start a docker instance. 9 | Steps to take are. 10 | 11 | ``` 12 | $ git clone https://github.com/CanonicalLtd/serial-vault 13 | $ cd serial-vault/docker-compose 14 | $ docker-compose up 15 | ``` 16 | 17 | remove containers after try: 18 | ``` 19 | $ docker-compose kill && docker-compose rm 20 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Serial Vault" 3 | table_of_contents: False 4 | --- 5 | 6 | # About Serial Vault 7 | 8 | The Serial Vault is a web service that generates signed serial assertions for devices. 9 | It can be run in a data centre or on premises on a factory LAN. 10 | 11 | The Serial Vault holds a list of approved models for a manufacturer, and the encrypted 12 | signing key(s) for the models. The service validates the model and logs if the serial number 13 | and device-key fingerprint have been previously used. 14 | 15 | Security around the SerialVault will be concerned about avoiding key or whole-system 16 | duplication, but it is left to the ODM/OEM to ensure physical security of the machine (theft) 17 | and ensuring that request to its Rest API only originate from within a trusted LAN or VPN. 18 | 19 | The hardware specification should be industrial grade for durability, rack mountable and high 20 | spec’d to reduce lag in response time as much as reasonably possible. 21 | 22 | It stores model information and the encrypted signing keys in a database, and can optionally 23 | use a TPM module as a part of the encryption process. 24 | -------------------------------------------------------------------------------- /docs/metadata.yaml: -------------------------------------------------------------------------------- 1 | site_title: serial-vault documentation 2 | site_logo_url: https://assets.ubuntu.com/v1/c5cb0f8e-picto-ubuntu.svg 3 | navigation: 4 | - title: Introduction 5 | children: 6 | - title: About Serial Vault 7 | location: index.md 8 | - title: Services Design 9 | location: design.md 10 | - title: Install & Configure 11 | children: 12 | - title: Installation 13 | location: installation.md 14 | - title: Configuration 15 | location: config.md 16 | - title: Install the Snap 17 | location: snap.md 18 | - title: Try it as Docker 19 | location: docker.md 20 | - title: Reference 21 | children: 22 | - title: Commands 23 | location: reference/commands.md 24 | - title: API Documentation 25 | location: reference/rest-api.md 26 | children: 27 | - title: /v1/version 28 | location: reference/rest-api/v1-version.md 29 | - title: /v1/request-id 30 | location: reference/rest-api/v1-request-id.md 31 | - title: /v1/serial 32 | location: reference/rest-api/v1-serial.md 33 | - title: Report a Bug 34 | location: report-bug.md 35 | -------------------------------------------------------------------------------- /docs/reference/commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Commands" 3 | table_of_contents: True 4 | --- 5 | 6 | # Admin tool 7 | 8 | Along with the snap comes a CLI administration tool. It allows to execute 9 | several operations that are also available through the web UI. In other cases 10 | they are only available here and even there is a case when it is needed to be 11 | executed here a command before accessing the UI. 12 | Here they are: 13 | 14 | ## serial-vault.admin account 15 | 16 | The *serial-vault.admin account* command allows you to cache accounts 17 | from the store in the database 18 | 19 | Example: 20 | 21 | ``` 22 | serial-vault.admin account cache 23 | ``` 24 | 25 | ## serial-vault.admin client 26 | 27 | Use *serial-vault.admin client* command to generate a test serial 28 | assertion request 29 | 30 | Example: 31 | ``` 32 | serial-vault.admin client -api=IFUyVnlhV0ZzSUZaaGRXeDB787o -brand=thebrand -model=pc -serial=B2011M -url=https://serial-vault/v1/ 33 | ``` 34 | 35 | ## serial-vault.admin database 36 | 37 | The *serial-vault.admin database* command creates or updates database tables 38 | and relations. Though this is executed just after service startup, this way can be also 39 | executed on demand 40 | 41 | Example: 42 | 43 | ``` 44 | serial-vault.admin database 45 | ``` 46 | 47 | ## serial-vault.admin user 48 | 49 | Use *serial-vault.admin user* to manage any operation related with 50 | Serial Vault users. You can add, list, delete or update users 51 | 52 | Some examples: 53 | 54 | ``` 55 | serial-vault.admin user list 56 | serial-vault.admin user add somenickname -n User -r superuser 57 | serial-vault.admin user update somenickname -n NewName 58 | serial-vault.admin user delete somenickname 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/reference/rest-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "REST API" 3 | table_of_contents: False 4 | --- 5 | 6 | # Serial Vault REST API 7 | 8 | The SerialVault will expose a REST API that can be used by the Gadget Snap that runs on the device. 9 | 10 | The following functionality needs to be exposed by the signing service API: 11 | 12 | * query version of the vault 13 | * query supported models 14 | * request device assertion creation 15 | -------------------------------------------------------------------------------- /docs/reference/rest-api/v1-request-id.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "/v1/request-id" 3 | table_of_contents: False 4 | --- 5 | 6 | ## POST /v1/request-id 7 | 8 | ### Description 9 | 10 | Returns a nonce that is needed for the 'serial' request. 11 | 12 | ### Request 13 | 14 | None. Though header must include model api-key 15 | ``` 16 | api-key: 17 | ``` 18 | ### Response 19 | 20 | ``` 21 | { 22 | "request-id": "abc123456", 23 | "success": true, 24 | "message": "" 25 | } 26 | ``` 27 | | Field | Description | 28 | |-------|-------------| 29 | | request-id* | unique string that is needed for serial requests (string) | 30 | | success* | whether the request was successful (bool) | 31 | | message* | error message from the request (string) | 32 | 33 | ### Errors 34 | 35 | The following errors can occur: 36 | 37 | * Error in retrieving the authentication token 38 | * The authentication token is invalid 39 | * Invalid API key used 40 | * delete-expired-nonces 41 | * generate-request-id error 42 | 43 | ### Example 44 | 45 | ``` 46 | wget --header='api-key: 47ladfh4la8009dafhYYZ0' https://serial-vault/v1/request-id 47 | { 48 | "request-id": "abc123456", 49 | "success": true, 50 | "message": "" 51 | } 52 | ``` -------------------------------------------------------------------------------- /docs/reference/rest-api/v1-version.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "/v1/version" 3 | table_of_contents: False 4 | --- 5 | 6 | ## GET /v1/version 7 | 8 | ### Description 9 | 10 | Returns the version of the Serial Vault web service 11 | 12 | ### Request 13 | 14 | None 15 | 16 | ### Response 17 | 18 | ``` 19 | { 20 | "version":"0.1.0", 21 | } 22 | ``` 23 | 24 | | Field | Description | 25 | |---------------|-----| 26 | | version | the version of the serial vault service (string) | 27 | 28 | ### Errors 29 | 30 | The following errors can occur: 31 | 32 | * Error in retrieving the authentication token 33 | * The authentication token is invalid 34 | * Error encoding the version response 35 | 36 | ### Example 37 | 38 | ``` 39 | wget https://serial-vault/v1/version 40 | { 41 | "version":"0.1.0", 42 | } 43 | ``` -------------------------------------------------------------------------------- /docs/report-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Report a Bug" 3 | table_of_contents: False 4 | --- 5 | 6 | # Rebort a Bug 7 | 8 | Bugs can be reported [here](https://github.com/CanonicalLtd/serial-vault/issues). 9 | 10 | When submitting a bug report, please attach as much information as possible -------------------------------------------------------------------------------- /factory-serial-vault/README.md: -------------------------------------------------------------------------------- 1 | # Factory Serial Vault 2 | 3 | The full suite of Serial Vault Services in the factory: 4 | 5 | - Signing Service: web service to sign the serial assertion for a device 6 | - Sync Service: scheduled task that syncs with a cloud Serial Vault 7 | 8 | ## Build the Factory Serial Vault Snap 9 | Install [snapcraft](https://snapcraft.io/) 10 | 11 | ```bash 12 | snapcraft cleanbuild 13 | ``` 14 | 15 | ## Install the Factory Serial Vault 16 | ```bash 17 | # sudo is only needed if you are not logged into the store 18 | sudo snap install --dangerous factory-serial-vault_*_amd64.snap 19 | ``` 20 | 21 | ## Configure the Factory Serial Vault 22 | To generate a `settings.yaml` configuration file: 23 | 24 | ```bash 25 | factory-serial-vault.serviceinit 26 | ``` 27 | 28 | This will display the settings.yaml file for the serial vault. To use the details, copy and 29 | paste them into a settings.yaml file and then run: 30 | ```bash 31 | cat settings.yaml | sudo factory-serial-vault.config 32 | sudo snap disable factory-serial-vault 33 | sudo snap enable factory-serial-vault 34 | ``` 35 | 36 | The local sqlite3 database will be generated and synchronized with the cloud serial vault. 37 | The factory database will include all the data needed to provide signed serial assertions 38 | to devices in the factory. 39 | 40 | The services are then accessible via: 41 | Signing Service : http://localhost/v1/version 42 | 43 | 44 | ## Set-up Apache and SSL 45 | Apache is configured to use HTTP by default. It is possible to use HTTPS by generating a self-signed certificate or 46 | supplying your own certificate using the ```enable-https``` command. 47 | ```bash 48 | sudo factory-serial-vault.enable-https -h 49 | ``` 50 | Note: snapd will not be able to use the signing API if a self-signed certificate is used. 51 | -------------------------------------------------------------------------------- /factory-serial-vault/bin/serviceinit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generate the serial-vault secrets 4 | keystore_secret=$(cat /dev/urandom | tr -dc A-Z-a-z-0-9 | head -c64) 5 | 6 | # Generate the configuration for the serial-vault 7 | echo "\n\nSave the config file settings to a file e.g. settings.yaml" 8 | echo "================================================" 9 | cat < 41 | Include ${SNAP}/conf/nossl.conf 42 | 43 | 44 | # Only enable SSL if requested (self-signed certs are enabled by default) 45 | 46 | Include ${SNAP}/conf/ssl.conf 47 | -------------------------------------------------------------------------------- /factory-serial-vault/src/apache/conf/nossl.conf: -------------------------------------------------------------------------------- 1 | 2 | Listen 443 3 | 4 | LoadModule ssl_module modules/mod_ssl.so 5 | 6 | # Virtual host for HTTP - hosting the three applications 7 | 8 | 9 | DocumentRoot "${SNAP}/static" 10 | 11 | AllowOverride None 12 | Require all granted 13 | 14 | 15 | ProxyPreserveHost on 16 | ProxyPassReverse / http://localhost:8080 17 | 18 | RewriteEngine on 19 | RewriteRule ^/(.*) http://localhost:8080/$1 [P,L] 20 | 21 | # Static files and other URLs handled by the admin service 22 | RewriteRule ^/$ "/index.html" [PT] 23 | 24 | -------------------------------------------------------------------------------- /factory-serial-vault/src/apache/conf/ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | Listen 443 3 | 4 | LoadModule ssl_module modules/mod_ssl.so 5 | 6 | # Virtual host for HTTP. All it does it redirect to HTTPS. 7 | 8 | RewriteEngine on 9 | # Disable HTTP TRACK method. 10 | RewriteCond %{REQUEST_METHOD} ^TRACK 11 | RewriteRule .* - [R=405,L] 12 | # Redirect everything else to HTTPS 13 | RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent] 14 | 15 | 16 | 17 | 18 | 19 | # Enable HSTS only if requested 20 | 21 | Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;" 22 | 23 | 24 | DocumentRoot "${SNAP}/static" 25 | 26 | AllowOverride None 27 | Require all granted 28 | 29 | 30 | SSLEngine On 31 | SSLProtocol all 32 | 33 | SSLCertificateFile ${SNAP_DATA}/certs/live/cert.pem 34 | SSLCertificateKeyFile ${SNAP_DATA}/certs/live/privkey.pem 35 | SSLCertificateChainFile ${SNAP_DATA}/certs/live/chain.pem 36 | 37 | ProxyPreserveHost on 38 | ProxyPassReverse / http://localhost:8080 39 | 40 | RequestHeader set X-Forwarded-Protocol "https" 41 | RequestHeader set X-Forwarded-Ssl "on" 42 | 43 | RewriteEngine on 44 | RewriteRule ^/(.*) http://localhost:8080/$1 [P,L] 45 | 46 | # Static files and other URLs handled by the admin service 47 | RewriteRule ^/$ "/index.html" [PT] 48 | 49 | -------------------------------------------------------------------------------- /factory-serial-vault/src/apache/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 24 |
25 |
26 |
27 |

28 | Serial Vault Services 29 |

30 |
31 |
32 | 33 | The Serial Vault is a web service that cryptographically signs Ubuntu Core serial assertions.
34 | The services offered are:
35 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | -------------------------------------------------------------------------------- /factory-serial-vault/src/apache/utilities/apache-utilities: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export APACHE_PIDFILE="$SNAP_DATA/httpd.pid" 4 | 5 | mkdir -p -m 755 "$(dirname $APACHE_PIDFILE)" 6 | 7 | restart_apache_if_running() 8 | { 9 | if [ -f "$APACHE_PIDFILE" ]; then 10 | # Restart apache by stopping it and letting systemd start it again. 11 | apache_pid=$(cat "$APACHE_PIDFILE") 12 | echo -n "Restarting apache... " 13 | output=$(httpd-wrapper -k stop 2>&1) 14 | if [ $? -eq 0 ]; then 15 | while kill -0 $apache_pid 2>/dev/null; do 16 | sleep 1 17 | done 18 | echo "done" 19 | else 20 | echo "error" 21 | echo "$output" 22 | fi 23 | fi 24 | } -------------------------------------------------------------------------------- /keystore/empty_report.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/keystore/empty_report.xml -------------------------------------------------------------------------------- /keystore/example_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 860-00014 7 | e4bf7ceb-f7af-442e-b734-5432ef3306b5 8 | Validation Test 9 | 2018-04-09T17:44:16+02:00 10 | 2018-04-09T17:44:16+02:00 11 | Failed 12 | 13 | 14 | 15 | factory_cpu/iMX6ULL 16 | failed 17 | 18 | 19 | factory_ethernet/card-detect 20 | failed 21 | 22 | 23 | factory_hdd/drive-count 24 | failed 25 | 26 | 27 | factory_RAM/size 28 | passed 29 | 30 | 31 | factory_RTC 32 | failed 33 | 34 | 35 | factory_usb/usb2-root-hub-present 36 | passed 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /keystore/placeholder.txt: -------------------------------------------------------------------------------- 1 | The default location for a filesystem keystore. 2 | 3 | A test key is provided that is used in the unit tests. 4 | -------------------------------------------------------------------------------- /launchers/cache-accounts-cron-job: -------------------------------------------------------------------------------- 1 | # Cron hourly to fetch account and account-key assertions from store 2 | @hourly root serial-vault-admin account cache 3 | -------------------------------------------------------------------------------- /launchers/serial-vault: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | {{ bindir }}/serial-vault-admin database --config={{ confdir }}/settings.yaml 4 | #TODO It is needed to modify serial-vault params to be set in same way as admin tool. 5 | #That would set coherence in both services using -- or - for long and short params. 6 | {{ bindir }}/serial-vault -config={{ confdir }}/settings.yaml 7 | -------------------------------------------------------------------------------- /launchers/serial-vault-admin: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | {{ bindir }}/serial-vault-admin --config={{ confdir }}/settings.yaml $@ 4 | -------------------------------------------------------------------------------- /manage/account.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | // AccountCommand is the main command for account management 23 | type AccountCommand struct { 24 | Cache AccountCacheCommand `command:"cache" alias:"c" description:"Cache the account assertions from the store in the database"` 25 | } 26 | -------------------------------------------------------------------------------- /manage/account_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "github.com/CanonicalLtd/serial-vault/account" 24 | "github.com/CanonicalLtd/serial-vault/datastore" 25 | "gopkg.in/check.v1" 26 | ) 27 | 28 | type AccountSuite struct{} 29 | 30 | var _ = check.Suite(&AccountSuite{}) 31 | 32 | func (s *AccountSuite) SetUpTest(c *check.C) { 33 | datastore.Environ = &datastore.Env{DB: &datastore.MockDB{}} 34 | 35 | // Mock the retrieval of the assertion from the store (using a fixed assertion) 36 | account.FetchAssertionFromStore = account.MockFetchAssertionFromStore 37 | } 38 | 39 | func (s *AccountSuite) TestAccount(c *check.C) { 40 | tests := []manTest{ 41 | { 42 | Args: []string{"serial-vault-admin", "account"}, 43 | ErrorMessage: "Please specify the cache command"}, 44 | { 45 | Args: []string{"serial-vault-admin", "account", "invalid"}, 46 | ErrorMessage: "Unknown command `invalid'. You should use the cache command"}, 47 | { 48 | Args: []string{"serial-vault-admin", "account", "cache"}, 49 | ErrorMessage: ""}, 50 | } 51 | 52 | for _, t := range tests { 53 | runTest(c, t.Args, t.ErrorMessage) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /manage/accountcache.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/CanonicalLtd/serial-vault/account" 26 | "github.com/CanonicalLtd/serial-vault/datastore" 27 | ) 28 | 29 | // AccountCacheCommand handles the caching of account assertions from the store. 30 | // This command would normally be run as a cron 31 | type AccountCacheCommand struct{} 32 | 33 | // Execute the caching of account assertions 34 | func (cmd AccountCacheCommand) Execute(args []string) error { 35 | 36 | openDatabase() 37 | 38 | // Cache the account/account-key assertions from the store in the database 39 | // (reads through the accounts and refreshes the account assertions) 40 | fmt.Println("Update account assertions from the Ubuntu store...") 41 | account.CacheAccounts(datastore.Environ) 42 | 43 | // Cache the account/account-key assertions from the store in the database 44 | // (This reads through the keypairs to get the accounts) 45 | fmt.Println("\nUpdate account/account-key assertions from the Ubuntu store...") 46 | account.CacheAccountAssertions(datastore.Environ) 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /manage/client_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "gopkg.in/check.v1" 24 | ) 25 | 26 | type ClientSuite struct{} 27 | 28 | var _ = check.Suite(&ClientSuite{}) 29 | 30 | func (s *ClientSuite) SetUpTest(c *check.C) { 31 | 32 | getRequestID = MockGetRequestID 33 | getSerial = MockSerial 34 | deviceKey = "../keystore/TestDeviceKey.asc" 35 | } 36 | 37 | func (s *ClientSuite) TestAccount(c *check.C) { 38 | tests := []manTest{ 39 | { 40 | Args: []string{"serial-vault-admin", "client"}, 41 | ErrorMessage: "the required flags `-a, --api', `-b, --brand', `-m, --model', `-s, --serial' and `-u, --url' were not specified"}, 42 | { 43 | Args: []string{"serial-vault-admin", "client", "invalid"}, 44 | ErrorMessage: "the required flags `-a, --api', `-b, --brand', `-m, --model', `-s, --serial' and `-u, --url' were not specified"}, 45 | { 46 | Args: []string{"serial-vault-admin", "client", "-a", "ValidAPIKey", "-b", "system", "-m", "alder", "-s", "A1234", "-u", "http://example.com/v1/"}, 47 | ErrorMessage: ""}, 48 | } 49 | 50 | for _, t := range tests { 51 | runTest(c, t.Args, t.ErrorMessage) 52 | } 53 | } 54 | 55 | func MockGetRequestID(url, apiKey string) (string, error) { 56 | return "abc1234", nil 57 | } 58 | 59 | func MockSerial(serialRequest, url, apiKey string) (string, error) { 60 | return "MOCK: serial", nil 61 | } 62 | -------------------------------------------------------------------------------- /manage/database_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "github.com/CanonicalLtd/serial-vault/service/log" 24 | 25 | "github.com/CanonicalLtd/serial-vault/config" 26 | "github.com/CanonicalLtd/serial-vault/datastore" 27 | 28 | "gopkg.in/check.v1" 29 | ) 30 | 31 | type databaseSuite struct{} 32 | 33 | var _ = check.Suite(&databaseSuite{}) 34 | 35 | func (s *databaseSuite) SetUpTest(c *check.C) { 36 | 37 | mockDB := datastore.MockDB{} 38 | config := config.Settings{KeyStoreType: "filesystem", KeyStorePath: "../keystore", KeyStoreSecret: "secret code to encrypt the auth-key hash"} 39 | datastore.Environ = &datastore.Env{DB: &mockDB, Config: config} 40 | datastore.OpenKeyStore(config) 41 | } 42 | 43 | func (s *databaseSuite) TestDoTable(c *check.C) { 44 | m1 := func() error { 45 | log.Println("Successful execution 1") 46 | return nil 47 | } 48 | 49 | m2 := func() error { 50 | log.Println("Successful execution 2") 51 | return nil 52 | } 53 | 54 | exec([]operation{ 55 | {m1, create, "the table 1", false}, 56 | {m2, update, "the table 2", false}, 57 | {m1, create, "the table 1", true}, 58 | {m2, update, "the table 2", true}, 59 | }) 60 | } 61 | 62 | func (s *databaseSuite) TestDatabase(c *check.C) { 63 | tests := []manTest{ 64 | { 65 | Args: []string{"serial-vault-admin", "database"}, 66 | ErrorMessage: ""}, 67 | } 68 | 69 | for _, t := range tests { 70 | runTest(c, t.Args, t.ErrorMessage) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /manage/manage.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "github.com/CanonicalLtd/serial-vault/config" 24 | "github.com/CanonicalLtd/serial-vault/datastore" 25 | ) 26 | 27 | // Command defines the options for the serial-vault-admin command-line utility 28 | type Command struct { 29 | SettingsFile string `short:"c" long:"config" description:"Path to the config file" default:"./settings.yaml"` 30 | 31 | Account AccountCommand `command:"account" alias:"a" description:"Account management"` 32 | Client ClientCommand `command:"client" alias:"c" description:"Serial-Vault Client to generate a test serial assertion request"` 33 | Database DatabaseCommand `command:"database" alias:"d" description:"Database schema update"` 34 | User UserCommand `command:"user" alias:"u" description:"User management"` 35 | } 36 | 37 | // Manage is the implementation of the command configuration for the serial-vault-admin command-line 38 | var Manage Command 39 | 40 | func openDatabase() { 41 | // Check that the database has not been set e.g. by a mock 42 | if datastore.Environ.DB != nil { 43 | return 44 | } 45 | 46 | config.ReadConfig(&datastore.Environ.Config, Manage.SettingsFile) 47 | 48 | // Open the connection to the database 49 | datastore.OpenSysDatabase(datastore.Environ.Config.Driver, datastore.Environ.Config.DataSource) 50 | } 51 | -------------------------------------------------------------------------------- /manage/manage_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "os" 24 | "testing" 25 | 26 | "github.com/jessevdk/go-flags" 27 | 28 | "gopkg.in/check.v1" 29 | ) 30 | 31 | // Hook up check.v1 into the "go test" runner 32 | func Test(t *testing.T) { check.TestingT(t) } 33 | 34 | type manTest struct { 35 | Args []string 36 | ErrorMessage string 37 | } 38 | 39 | func mockArgs(args ...string) (restore func()) { 40 | old := os.Args 41 | os.Args = args 42 | return func() { os.Args = old } 43 | } 44 | 45 | func RunMain() error { 46 | // Parse the command line arguments and execute the command 47 | parser := flags.NewParser(&Manage, flags.HelpFlag) 48 | _, err := parser.Parse() 49 | return err 50 | } 51 | 52 | func runTest(c *check.C, args []string, errorMessage string) { 53 | restore := mockArgs(args...) 54 | defer restore() 55 | 56 | err := RunMain() 57 | 58 | if len(errorMessage) == 0 { 59 | c.Check(err, check.IsNil) 60 | } else { 61 | c.Assert(err, check.ErrorMatches, errorMessage) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /manage/user.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import "fmt" 23 | 24 | // UserCommand is the main command for user management 25 | type UserCommand struct { 26 | List UserListCommand `command:"list" alias:"ls" alias:"l" description:"List the users"` 27 | Add UserAddCommand `command:"add" alias:"a" description:"Add a new user"` 28 | Update UserUpdateCommand `command:"update" alias:"a" description:"Update an existing user"` 29 | Delete UserDeleteCommand `command:"delete" alias:"d" description:"Delete an existing user"` 30 | } 31 | 32 | func checkUsernameArg(args []string, action string) error { 33 | switch len(args) { 34 | case 0: 35 | return fmt.Errorf("%s user expects a 'username' argument", action) 36 | case 1: 37 | return nil 38 | default: 39 | return fmt.Errorf("%s user expects a single 'username' argument", action) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /manage/useradd.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/CanonicalLtd/serial-vault/datastore" 26 | ) 27 | 28 | // UserAddCommand handles adding a new user for the serial-vault-admin command 29 | type UserAddCommand struct { 30 | Name string `short:"n" long:"name" description:"Full name of the user" required:"yes"` 31 | RoleName string `short:"r" long:"role" description:"Role of the user" required:"yes" choice:"standard" choice:"admin" choice:"superuser"` 32 | Email string `short:"e" long:"email" description:"Email of the user"` 33 | } 34 | 35 | // Execute the adding a new user 36 | func (cmd UserAddCommand) Execute(args []string) error { 37 | 38 | err := checkUsernameArg(args, "Add") 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // Convert the rolename to an ID 44 | roleID, ok := datastore.RoleID[cmd.RoleName] 45 | if !ok { 46 | return fmt.Errorf("Cannot find the role ID for role '%s'", cmd.RoleName) 47 | } 48 | 49 | // Open the database and create the user 50 | openDatabase() 51 | user := datastore.User{ 52 | Username: args[0], 53 | Name: cmd.Name, 54 | Role: roleID, 55 | Email: cmd.Email, 56 | Accounts: []datastore.Account{}, 57 | } 58 | 59 | _, err = datastore.Environ.DB.CreateUser(user) 60 | if err != nil { 61 | return fmt.Errorf("Error creating the user: %v", err) 62 | } 63 | 64 | fmt.Printf("User '%s' created successfully\n", user.Username) 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /manage/userdelete.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/CanonicalLtd/serial-vault/datastore" 26 | ) 27 | 28 | // UserDeleteCommand handles user delete for the serial-vault-admin command 29 | type UserDeleteCommand struct{} 30 | 31 | // Execute user deletion 32 | func (cmd UserDeleteCommand) Execute(args []string) error { 33 | err := checkUsernameArg(args, "Delete") 34 | if err != nil { 35 | return err 36 | } 37 | 38 | // Open the database and get the user from the database 39 | openDatabase() 40 | user, err := datastore.Environ.DB.GetUserByUsername(args[0]) 41 | if err != nil { 42 | return fmt.Errorf("Error finding the user '%s'", args[0]) 43 | } 44 | 45 | err = datastore.Environ.DB.DeleteUser(user.ID) 46 | if err != nil { 47 | return fmt.Errorf("Error deleting the user: %v", err) 48 | } 49 | 50 | fmt.Printf("User '%s' deleted successfully\n", user.Username) 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /manage/userlist.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package manage 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | "text/tabwriter" 26 | 27 | "github.com/CanonicalLtd/serial-vault/datastore" 28 | ) 29 | 30 | // UserListCommand handles the list of users for the serial-vault-admin command 31 | type UserListCommand struct{} 32 | 33 | // Execute the list of users 34 | func (cmd UserListCommand) Execute(args []string) error { 35 | 36 | // Get the list of users from the database 37 | openDatabase() 38 | users, err := datastore.Environ.DB.ListUsers() 39 | if err != nil { 40 | fmt.Printf("Error list the users: %v\n", err) 41 | } 42 | 43 | // Create a tabwriter to format the output 44 | w := new(tabwriter.Writer) 45 | w.Init(os.Stdout, 5, 0, 4, ' ', 0) 46 | 47 | // Print the headers 48 | fmt.Fprintln(w, "") 49 | fmt.Fprintln(w, "Username\tName\tRole\tEmail") 50 | 51 | // Print the user list 52 | for _, u := range users { 53 | role, _ := datastore.RoleName[u.Role] 54 | 55 | s := fmt.Sprintf("%s\t%s\t%s\t%s", u.Username, u.Name, role, u.Email) 56 | fmt.Fprintln(w, s) 57 | } 58 | fmt.Fprintln(w, "") 59 | w.Flush() 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /mdlint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # see http://daringfireball.net/projects/markdown/syntax 4 | # for the "canonical" reference 5 | # 6 | # We support django-markdown which uses python-markdown, see: 7 | # http://pythonhosted.org/Markdown/ 8 | 9 | import sys 10 | import codecs 11 | 12 | def lint_li(fname, text): 13 | """Ensure that the list-items are multiplies of 4""" 14 | is_clean = True 15 | for i, line in enumerate(text.splitlines()): 16 | if line.lstrip().startswith("*") and line.index("*") % 4 != 0: 17 | print("%s: line %i list has non-4 spaces indent" % (fname, i)) 18 | is_clean = False 19 | return is_clean 20 | 21 | 22 | def lint(md_files): 23 | """lint all md files""" 24 | all_clean = True 25 | for md in md_files: 26 | with codecs.open(md, "r", "utf-8") as f: 27 | buf = f.read() 28 | for fname, func in globals().items(): 29 | if fname.startswith("lint_"): 30 | all_clean &= func(md, buf) 31 | return all_clean 32 | 33 | 34 | if __name__ == "__main__": 35 | if not lint(sys.argv): 36 | sys.exit(1) 37 | -------------------------------------------------------------------------------- /ols-vms.conf: -------------------------------------------------------------------------------- 1 | [serial-vault] 2 | 3 | vm.class = lxd 4 | vm.architecture = amd64 5 | vm.release = xenial 6 | vm.update = True 7 | vm.packages = @dependencies.txt, @dependencies-devel.txt, @charm/packages.txt 8 | jenkaas.secrets = swift/serial-vault:.config/swift/serial-vault 9 | sideload.snaps = snapd_19122 core22_1564 10 | # go_10679 is go 1.22.6 from 1.22/stable 11 | sideload.classic_snaps = charm_745 codetree_5 go_10679 12 | -------------------------------------------------------------------------------- /random/random.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package random 21 | 22 | import ( 23 | "encoding/base64" 24 | "math/rand" 25 | ) 26 | 27 | // GenerateRandomBytes returns securely generated random bytes. 28 | // It will return an error if the system's secure random 29 | // number generator fails to function correctly, in which 30 | // case the caller should not continue. 31 | func GenerateRandomBytes(n int) ([]byte, error) { 32 | b := make([]byte, n) 33 | _, err := rand.Read(b) 34 | // Note that err == nil only if we read len(b) bytes. 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return b, nil 40 | } 41 | 42 | // GenerateRandomString returns a URL-safe, base64 encoded 43 | // securely generated random string. 44 | func GenerateRandomString(s int) (string, error) { 45 | b, err := GenerateRandomBytes(s) 46 | return base64.URLEncoding.EncodeToString(b), err 47 | } 48 | -------------------------------------------------------------------------------- /random/random_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package random 21 | 22 | import "testing" 23 | 24 | func TestRandomGeneration(t *testing.T) { 25 | n := 10 26 | // search for random strings enough smalls as to see if they are random 27 | tokens := make(map[string]string) 28 | for i := 0; i < n; i++ { 29 | // generate minimum amount of random data to verify it is enough random 30 | token, err := GenerateRandomString(10) 31 | if err != nil { 32 | t.Errorf("Error generating random string: %v", err) 33 | } 34 | tokens[token] = token 35 | } 36 | 37 | // Check that we have n different tokens stored in the map 38 | if len(tokens) < n { 39 | t.Error("Generated random numbers are not unique") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /serial-vault-snap/README.md: -------------------------------------------------------------------------------- 1 | # Serial Vault Snap 2 | 3 | Temporarily move the serial-vault snap to a sub-folder. 4 | 5 | TODO: move the snap to it's own repo. This snap is no longer actively used. 6 | -------------------------------------------------------------------------------- /serial-vault-snap/snap/gui/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/serial-vault-snap/snap/gui/icon.png -------------------------------------------------------------------------------- /serial-vault-snap/snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: serial-vault 2 | version: 2.1-1-dev 3 | summary: Service for the Serial Vault that signs serial assertions 4 | description: | 5 | The Serial Vault is a web service that generates signed serial assertions for devices. 6 | It can be run in a data centre or on premises on a factory LAN. 7 | The Serial Vault holds a list of approved models for a manufacturer, and the encrypted 8 | signing key(s) for the models. The service validates the model and logs if the serial 9 | number and device-key fingerprint have been previously used. 10 | Please find the source of this snap at: 11 | https://code.launchpad.net/~snappy-hwe-team/snappy-hwe-snaps/+git/serial-vault 12 | confinement: strict 13 | grade: stable 14 | 15 | apps: 16 | service: 17 | command: bin/snap-run-service 18 | plugs: 19 | - network 20 | - network-bind 21 | daemon: simple 22 | config: 23 | command: bin/set-config 24 | admin: 25 | command: bin/snap-run-admin 26 | plugs: 27 | - network 28 | parts: 29 | service: 30 | plugin: go 31 | source: . 32 | go-importpath: github.com/CanonicalLtd/serial-vault 33 | build-packages: 34 | # needed by go get 35 | - bzr 36 | install: | 37 | # set environment var SKIP_TESTS to 'y' or 'yes' if you want not to execute 38 | # this part unit tests in your next compilation. 39 | if [ "$SKIP_TESTS" = "yes" ] || [ "$SKIP_TESTS" = "y" ]; then 40 | echo "skipping unit tests" 41 | else 42 | export GOPATH=$PWD/../go 43 | cd $GOPATH/src/github.com/CanonicalLtd/serial-vault 44 | ./run-checks all 45 | fi 46 | bin: 47 | source: bin 48 | plugin: dump 49 | organize: 50 | "*": bin/ 51 | static: 52 | source: static 53 | plugin: dump 54 | organize: 55 | "*": static/ 56 | # Private key for the vaultclient 57 | keystore: 58 | source: keystore 59 | plugin: dump 60 | organize: 61 | TestDeviceKey.asc: keystore/TestDeviceKey.asc 62 | -------------------------------------------------------------------------------- /service/account/handlers_adminapi.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package account 22 | 23 | import ( 24 | "net/http" 25 | 26 | "github.com/CanonicalLtd/serial-vault/service/request" 27 | "github.com/CanonicalLtd/serial-vault/service/response" 28 | ) 29 | 30 | // APIList is the API method to fetch the sub-store models 31 | func APIList(w http.ResponseWriter, r *http.Request) { 32 | // Validate the user and API key 33 | user, err := request.CheckUserAPI(r) 34 | if err != nil { 35 | response.FormatStandardResponse(false, "error-auth", "", err.Error(), w) 36 | return 37 | } 38 | 39 | // Call the API with the user 40 | listHandler(w, user, true) 41 | } 42 | -------------------------------------------------------------------------------- /service/app/handlers_app.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package app 21 | 22 | import ( 23 | "net/http" 24 | "strings" 25 | "text/template" 26 | 27 | "github.com/CanonicalLtd/serial-vault/service/log" 28 | 29 | "github.com/CanonicalLtd/serial-vault/datastore" 30 | ) 31 | 32 | // IndexTemplate is the path to the HTML template 33 | var IndexTemplate = "/static/app.html" 34 | 35 | // Page is the page details for the web application 36 | type Page struct { 37 | Title string 38 | Logo string 39 | } 40 | 41 | // Index is the front page of the web application 42 | func Index(w http.ResponseWriter, r *http.Request) { 43 | page := Page{Title: datastore.Environ.Config.Title, Logo: datastore.Environ.Config.Logo} 44 | 45 | path := []string{datastore.Environ.Config.DocRoot, IndexTemplate} 46 | t, err := template.ParseFiles(strings.Join(path, "")) 47 | if err != nil { 48 | log.Printf("Error loading the application template: %v\n", err) 49 | http.Error(w, err.Error(), http.StatusInternalServerError) 50 | return 51 | } 52 | err = t.Execute(w, page) 53 | if err != nil { 54 | http.Error(w, err.Error(), http.StatusInternalServerError) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /service/app/handlers_app_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package app_test 21 | 22 | import ( 23 | "net/http" 24 | "net/http/httptest" 25 | "testing" 26 | 27 | "github.com/CanonicalLtd/serial-vault/config" 28 | "github.com/CanonicalLtd/serial-vault/datastore" 29 | "github.com/CanonicalLtd/serial-vault/service/app" 30 | ) 31 | 32 | func TestIndexHandler(t *testing.T) { 33 | 34 | app.IndexTemplate = "../../static/app.html" 35 | 36 | config := config.Settings{Title: "Site Title", Logo: "/url"} 37 | datastore.Environ = &datastore.Env{Config: config} 38 | 39 | w := httptest.NewRecorder() 40 | r, _ := http.NewRequest("GET", "/", nil) 41 | http.HandlerFunc(app.Index).ServeHTTP(w, r) 42 | 43 | if w.Code != http.StatusOK { 44 | t.Errorf("Expected status %d, got: %d", http.StatusOK, w.Code) 45 | } 46 | } 47 | 48 | func TestIndexHandlerInvalidTemplate(t *testing.T) { 49 | 50 | app.IndexTemplate = "../../static/does_not_exist.html" 51 | 52 | config := config.Settings{Title: "Site Title", Logo: "/url"} 53 | datastore.Environ = &datastore.Env{Config: config} 54 | 55 | w := httptest.NewRecorder() 56 | r, _ := http.NewRequest("GET", "/", nil) 57 | http.HandlerFunc(app.Index).ServeHTTP(w, r) 58 | 59 | if w.Code != http.StatusInternalServerError { 60 | t.Errorf("Expected status %d, got: %d", http.StatusInternalServerError, w.Code) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /service/assertion/handlers_api.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package assertion 22 | 23 | import ( 24 | "encoding/json" 25 | "io" 26 | "net/http" 27 | 28 | "github.com/CanonicalLtd/serial-vault/service/log" 29 | "github.com/CanonicalLtd/serial-vault/service/request" 30 | "github.com/CanonicalLtd/serial-vault/service/response" 31 | ) 32 | 33 | // ModelAssertionRequest is the JSON version of a model assertion request 34 | type ModelAssertionRequest struct { 35 | BrandID string `json:"brand-id"` 36 | Name string `json:"model"` 37 | } 38 | 39 | // ModelAssertion is the API method to generate a model assertion 40 | func ModelAssertion(w http.ResponseWriter, r *http.Request) response.ErrorResponse { 41 | // Validate the model API key 42 | apiKey, err := request.CheckModelAPI(r) 43 | if err != nil { 44 | log.Message("MODEL", response.ErrorInvalidAPIKey.Code, response.ErrorInvalidAPIKey.Message) 45 | return response.ErrorInvalidAPIKey 46 | } 47 | 48 | defer r.Body.Close() 49 | 50 | // Decode the JSON body 51 | request := ModelAssertionRequest{} 52 | err = json.NewDecoder(r.Body).Decode(&request) 53 | switch { 54 | // Check we have some data 55 | case err == io.EOF: 56 | return response.ErrorEmptyData 57 | // Check for parsing errors 58 | case err != nil: 59 | return response.ErrorResponse{Success: false, Code: response.ErrorDecodeJSON.Code, Message: err.Error(), StatusCode: http.StatusBadRequest} 60 | } 61 | 62 | return modelAssertionHandler(w, apiKey, request) 63 | } 64 | -------------------------------------------------------------------------------- /service/auth.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/CanonicalLtd/serial-vault/datastore" 8 | "github.com/CanonicalLtd/serial-vault/service/auth" 9 | "github.com/CanonicalLtd/serial-vault/usso" 10 | jwt "github.com/golang-jwt/jwt/v5" 11 | ) 12 | 13 | func getUserFromJWT(w http.ResponseWriter, r *http.Request) (datastore.User, error) { 14 | token, err := auth.JWTCheck(w, r) 15 | if err != nil { 16 | return datastore.User{}, err 17 | } 18 | 19 | // Null token means that auth is not enabled. 20 | if token == nil { 21 | return datastore.User{}, nil 22 | } 23 | 24 | claims := token.Claims.(jwt.MapClaims) 25 | username := claims[usso.ClaimsUsername].(string) 26 | role := int(claims[usso.ClaimsRole].(float64)) 27 | 28 | return datastore.User{ 29 | Username: username, 30 | Role: role, 31 | }, nil 32 | } 33 | 34 | func checkUserPermissions(user datastore.User, minimumAuthorizedRole int) error { 35 | // User authentication is turned off 36 | if !datastore.Environ.Config.EnableUserAuth { 37 | // Superuser permissions don't allow turned off authentication 38 | if minimumAuthorizedRole == datastore.Superuser { 39 | return errors.New("The user is not authorized") 40 | } 41 | return nil 42 | } 43 | 44 | if user.Role < minimumAuthorizedRole { 45 | return errors.New("The user is not authorized") 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /service/auth/auth.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package auth 21 | 22 | import ( 23 | "errors" 24 | "net/http" 25 | 26 | "github.com/CanonicalLtd/serial-vault/datastore" 27 | "github.com/CanonicalLtd/serial-vault/usso" 28 | jwt "github.com/golang-jwt/jwt/v5" 29 | ) 30 | 31 | // GetUserFromJWT retrieves the user details from the JSON Web Token 32 | func GetUserFromJWT(w http.ResponseWriter, r *http.Request) (datastore.User, error) { 33 | token, err := JWTCheck(w, r) 34 | if err != nil { 35 | return datastore.User{}, err 36 | } 37 | 38 | // Null token means that auth is not enabled. 39 | if token == nil { 40 | return datastore.User{}, nil 41 | } 42 | 43 | claims := token.Claims.(jwt.MapClaims) 44 | username := claims[usso.ClaimsUsername].(string) 45 | role := int(claims[usso.ClaimsRole].(float64)) 46 | 47 | return datastore.User{ 48 | Username: username, 49 | Role: role, 50 | }, nil 51 | } 52 | 53 | // CheckUserPermissions verifies that a user has a minimum role 54 | func CheckUserPermissions(user datastore.User, minimumAuthorizedRole int, apiCall bool) error { 55 | // User authentication is turned off (ignore if this is an Admin API call) 56 | if !apiCall && !datastore.Environ.Config.EnableUserAuth { 57 | // Superuser permissions don't allow turned off authentication 58 | if minimumAuthorizedRole == datastore.Superuser { 59 | return errors.New("The user is not authorized") 60 | } 61 | return nil 62 | } 63 | 64 | if user.Role < minimumAuthorizedRole { 65 | return errors.New("The user is not authorized") 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /service/auth/middleware.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package auth 21 | 22 | import ( 23 | "errors" 24 | "net/http" 25 | 26 | "github.com/CanonicalLtd/serial-vault/service/log" 27 | 28 | "github.com/CanonicalLtd/serial-vault/datastore" 29 | "github.com/CanonicalLtd/serial-vault/usso" 30 | jwt "github.com/golang-jwt/jwt/v5" 31 | ) 32 | 33 | // JWTCheck extracts the JWT from the request, validates it and returns the token 34 | func JWTCheck(w http.ResponseWriter, r *http.Request) (*jwt.Token, error) { 35 | 36 | // Do not validate access if user authentication is off (default) 37 | if !datastore.Environ.Config.EnableUserAuth { 38 | return nil, nil 39 | } 40 | 41 | // Get the JWT from the header or cookie 42 | jwtToken, err := usso.JWTExtractor(r) 43 | if err != nil { 44 | log.Println("Error in JWT extraction:", err.Error()) 45 | return nil, errors.New("Error in retrieving the authentication token") 46 | } 47 | 48 | // Verify the JWT string 49 | token, err := usso.VerifyJWT(jwtToken) 50 | if err != nil { 51 | log.Printf("JWT fails verification: %v", err.Error()) 52 | return nil, errors.New("The authentication token is invalid") 53 | } 54 | 55 | if !token.Valid { 56 | log.Println("Invalid JWT") 57 | return nil, errors.New("The authentication token is invalid") 58 | } 59 | 60 | // Set up the bearer token in the header 61 | w.Header().Set("Authorization", "Bearer "+jwtToken) 62 | 63 | return token, nil 64 | } 65 | -------------------------------------------------------------------------------- /service/metric/handler.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | 7 | "github.com/prometheus/client_golang/prometheus/promhttp" 8 | ) 9 | 10 | // Server is an http Metrics server. 11 | type Server struct { 12 | metrics http.Handler 13 | } 14 | 15 | var doOnce sync.Once 16 | 17 | // NewServer returns a new metrics server. 18 | func NewServer() *Server { 19 | doOnce.Do(func() { 20 | InitMetrics() 21 | }) 22 | return &Server{ 23 | metrics: promhttp.Handler(), 24 | } 25 | } 26 | 27 | // ServeHTTP returns a metrics server. 28 | // Use it to set up the prometheus endpoint in your web service 29 | // router := mux.NewRouter() 30 | // router.Handle("/_status/metrics", metric.NewServer()).Methods("GET") 31 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 32 | s.metrics.ServeHTTP(w, r) 33 | } 34 | -------------------------------------------------------------------------------- /service/metric/handler_test.go: -------------------------------------------------------------------------------- 1 | package metric_test 2 | 3 | import ( 4 | "net/http/httptest" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/CanonicalLtd/serial-vault/service/metric" 9 | ) 10 | 11 | func TestMetricHandler(t *testing.T) { 12 | w := httptest.NewRecorder() 13 | r := httptest.NewRequest("GET", "/", nil) 14 | metric.NewServer().ServeHTTP(w, r) 15 | if w.Code != 200 { 16 | t.Errorf("expected code 200, got %d", w.Code) 17 | } 18 | if !strings.HasPrefix(w.Header().Get("Content-Type"), "text/plain") { 19 | t.Errorf("expected Content-Type: 'text/plain', got %s", w.Header().Get("Content-Type")) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/metric/metrics.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | ) 6 | 7 | // HTTPIncomingRequestCounterVec is prometheus metric for incoming http requests count 8 | var HTTPIncomingRequestCounterVec = prometheus.NewCounterVec( 9 | prometheus.CounterOpts{ 10 | Name: "http_in_requests", 11 | Help: "metric for incoming HTTP requests count", 12 | }, 13 | []string{"method", "status", "view"}, 14 | ) 15 | 16 | // HTTPIncomingLatencyHistogramVec is prometheus metric for incoming requests latency 17 | var HTTPIncomingLatencyHistogramVec = prometheus.NewHistogramVec( 18 | prometheus.HistogramOpts{ 19 | Name: "http_in_latency", 20 | Help: "metric for incoming requests latency", 21 | Buckets: []float64{4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192}, 22 | }, 23 | []string{"method", "status", "view"}, 24 | ) 25 | 26 | // HTTPIncomingErrorsCounterVec is prometheus metric for HTTP errors 27 | var HTTPIncomingErrorsCounterVec = prometheus.NewCounterVec( 28 | prometheus.CounterOpts{ 29 | Name: "http_in_errors", 30 | Help: "metric for HTTP errors", 31 | }, 32 | []string{"method", "status", "view"}, 33 | ) 34 | 35 | // HTTPIncomingTimeoutsCounterVec is metric for incoming http timeouts 36 | var HTTPIncomingTimeoutsCounterVec = prometheus.NewCounterVec( 37 | prometheus.CounterOpts{ 38 | Name: "http_in_timeouts", 39 | Help: "metric for incoming HTTP timeouts", 40 | }, 41 | []string{"method", "view"}, 42 | ) 43 | 44 | // InitMetrics register all the metrics 45 | func InitMetrics() { 46 | prometheus.MustRegister(HTTPIncomingRequestCounterVec) 47 | prometheus.MustRegister(HTTPIncomingLatencyHistogramVec) 48 | prometheus.MustRegister(HTTPIncomingErrorsCounterVec) 49 | prometheus.MustRegister(HTTPIncomingTimeoutsCounterVec) 50 | } 51 | -------------------------------------------------------------------------------- /service/metric/middleware.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | // CollectAPIStats middleware collects different statistics from the HTTP request 10 | // use this middleware like this: 11 | // router := mux.NewRouter() 12 | // router.Handle("/v1/models", metric.CollectAPIStats("modelList", http.HandlerFunc(model.List))).Methods("GET") 13 | func CollectAPIStats(view string, inner http.Handler) http.Handler { 14 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | ww := &recordResponse{ResponseWriter: w} 16 | start := time.Now() 17 | 18 | inner.ServeHTTP(ww, r) 19 | 20 | // count all server side errors 21 | if ww.status >= 500 { 22 | HTTPIncomingErrorsCounterVec.WithLabelValues(r.Method, ww.Status(), view).Inc() 23 | } 24 | // count 504 Gateway Timeout 25 | if ww.status == 504 { 26 | HTTPIncomingTimeoutsCounterVec.WithLabelValues(r.Method, view).Inc() 27 | } 28 | latency := float64(time.Since(start).Milliseconds()) 29 | HTTPIncomingLatencyHistogramVec.WithLabelValues(r.Method, ww.Status(), view).Observe(latency) 30 | HTTPIncomingRequestCounterVec.WithLabelValues(r.Method, ww.Status(), view).Inc() 31 | }) 32 | } 33 | 34 | // recordResponse is a proxy around an http.ResponseWriter 35 | type recordResponse struct { 36 | http.ResponseWriter 37 | status int 38 | bytesWritten int64 39 | wroteHeader bool 40 | } 41 | 42 | // WriteHeader writes http status code 43 | func (r *recordResponse) WriteHeader(code int) { 44 | if !r.wroteHeader { 45 | r.status = code 46 | r.wroteHeader = true 47 | } 48 | r.ResponseWriter.WriteHeader(code) 49 | } 50 | 51 | // Write records written bytes 52 | func (r *recordResponse) Write(b []byte) (int, error) { 53 | if !r.wroteHeader { 54 | r.WriteHeader(http.StatusOK) 55 | } 56 | n, err := r.ResponseWriter.Write(b) 57 | r.bytesWritten += int64(n) 58 | return n, err 59 | } 60 | 61 | // Status returns the HTTP status of the request as a string 62 | func (r *recordResponse) Status() string { 63 | return strconv.Itoa(r.status) 64 | } 65 | -------------------------------------------------------------------------------- /service/request/request.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package request 21 | 22 | import ( 23 | "errors" 24 | "net/http" 25 | 26 | "github.com/CanonicalLtd/serial-vault/datastore" 27 | ) 28 | 29 | // CheckUserAPI validates the user and API key 30 | func CheckUserAPI(r *http.Request) (datastore.User, error) { 31 | // Get the user and API key from the header 32 | username := r.Header.Get("user") 33 | apiKey := r.Header.Get("api-key") 34 | 35 | // Find the user by API key 36 | return datastore.Environ.DB.GetUserByAPIKey(apiKey, username) 37 | } 38 | 39 | // CheckModelAPI the API key header to make sure it is an allowed header 40 | func CheckModelAPI(r *http.Request) (string, error) { 41 | apiKey := r.Header.Get("api-key") 42 | if len(apiKey) == 0 { 43 | return apiKey, errors.New("Blank API key used") 44 | } 45 | 46 | if ok := datastore.Environ.DB.CheckAPIKey(apiKey); !ok { 47 | return apiKey, errors.New("Unauthorized API key used") 48 | } 49 | 50 | return apiKey, nil 51 | } 52 | -------------------------------------------------------------------------------- /service/response/response.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2016-2017 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package response 21 | 22 | import ( 23 | "encoding/json" 24 | "net/http" 25 | "net/http/httptest" 26 | 27 | "github.com/CanonicalLtd/serial-vault/service/log" 28 | ) 29 | 30 | // JSONHeader is the JSON HTTP header 31 | const JSONHeader = "application/json; charset=UTF-8" 32 | 33 | // StandardResponse is the JSON response from an API method, indicating success or failure. 34 | type StandardResponse struct { 35 | Success bool `json:"success"` 36 | ErrorCode string `json:"error_code"` 37 | ErrorSubcode string `json:"error_subcode"` 38 | ErrorMessage string `json:"message"` 39 | } 40 | 41 | // FormatStandardResponse returns a JSON response from an API method, indicating success or failure. 42 | func FormatStandardResponse(success bool, errorCode, errorSubcode, message string, w http.ResponseWriter) error { 43 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 44 | response := StandardResponse{Success: success, ErrorCode: errorCode, ErrorSubcode: errorSubcode, ErrorMessage: message} 45 | 46 | if !response.Success { 47 | w.WriteHeader(http.StatusBadRequest) 48 | } 49 | 50 | // Encode the response as JSON 51 | if err := json.NewEncoder(w).Encode(response); err != nil { 52 | log.Printf("Error forming the boolean response (%v)\n. %v", response, err) 53 | return err 54 | } 55 | return nil 56 | } 57 | 58 | // ParseStandardResponse parses the response body and returns a standard response object 59 | func ParseStandardResponse(w *httptest.ResponseRecorder) (StandardResponse, error) { 60 | // Check the JSON response 61 | result := StandardResponse{} 62 | err := json.NewDecoder(w.Body).Decode(&result) 63 | return result, err 64 | } 65 | -------------------------------------------------------------------------------- /service/signinglog/actions_sync.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2018 Canonical Ltd 5 | * License granted by Canonical Limited 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 3 as 9 | * published by the Free Software Foundation. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | package signinglog 22 | 23 | import ( 24 | "net/http" 25 | 26 | "github.com/CanonicalLtd/serial-vault/datastore" 27 | "github.com/CanonicalLtd/serial-vault/service/auth" 28 | "github.com/CanonicalLtd/serial-vault/service/response" 29 | ) 30 | 31 | func syncLogHandler(w http.ResponseWriter, user datastore.User, apiCall bool, signLog datastore.SigningLog) { 32 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 33 | 34 | err := auth.CheckUserPermissions(user, datastore.SyncUser, apiCall) 35 | if err != nil { 36 | response.FormatStandardResponse(false, "error-auth", "", "", w) 37 | return 38 | } 39 | 40 | // Create the the signing-log if it does not exist 41 | exists, err := datastore.Environ.DB.CheckForMatching(signLog) 42 | if err != nil { 43 | response.FormatStandardResponse(false, "error-signinglog-match", "", err.Error(), w) 44 | return 45 | } 46 | 47 | if !exists { 48 | // The signing log has not been sync-ed, so create it (keep the same create timestamp) 49 | err = datastore.Environ.DB.CreateSigningLogSync(signLog) 50 | if err != nil { 51 | response.FormatStandardResponse(false, "error-signinglog-create", "", err.Error(), w) 52 | return 53 | } 54 | } 55 | 56 | // Return successful JSON response 57 | w.WriteHeader(http.StatusOK) 58 | response.FormatStandardResponse(true, "", "", "", w) 59 | } 60 | -------------------------------------------------------------------------------- /service/signinglog/handlers_signinglog.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package signinglog 21 | 22 | import ( 23 | "net/http" 24 | 25 | "github.com/CanonicalLtd/serial-vault/service/auth" 26 | "github.com/CanonicalLtd/serial-vault/service/response" 27 | "github.com/gorilla/mux" 28 | ) 29 | 30 | // List is the API method to fetch the log records from signing 31 | func List(w http.ResponseWriter, r *http.Request) { 32 | authUser, err := auth.GetUserFromJWT(w, r) 33 | if err != nil { 34 | response.FormatStandardResponse(false, "error-auth", "", err.Error(), w) 35 | return 36 | } 37 | 38 | listHandler(w, authUser, false) 39 | } 40 | 41 | // ListForAccount is the API method to fetch the log records from signing for an account 42 | func ListForAccount(w http.ResponseWriter, r *http.Request) { 43 | authUser, err := auth.GetUserFromJWT(w, r) 44 | if err != nil { 45 | response.FormatStandardResponse(false, "error-auth", "", err.Error(), w) 46 | return 47 | } 48 | 49 | vars := mux.Vars(r) 50 | params := GetSigningLogParams(r) 51 | 52 | listForAccountHandler(w, authUser, false, vars["authorityID"], params) 53 | } 54 | 55 | // ListFilters is the API method to fetch the log filter values 56 | func ListFilters(w http.ResponseWriter, r *http.Request) { 57 | authUser, err := auth.GetUserFromJWT(w, r) 58 | if err != nil { 59 | response.FormatStandardResponse(false, "error-auth", "", err.Error(), w) 60 | return 61 | } 62 | 63 | vars := mux.Vars(r) 64 | 65 | listFiltersHandler(w, authUser, false, vars["authorityID"]) 66 | } 67 | -------------------------------------------------------------------------------- /service/status/handler.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/CanonicalLtd/serial-vault/datastore" 8 | "github.com/CanonicalLtd/serial-vault/service/response" 9 | "github.com/gorilla/mux" 10 | ) 11 | 12 | // AddStatusEndpoints adds a gorilla/mux Subrouter with additional endpoints 13 | func AddStatusEndpoints(prefix string, r *mux.Router) { 14 | s := r.PathPrefix(prefix).Subrouter() 15 | 16 | s.HandleFunc("/ping", PingHandler). 17 | Methods("GET") 18 | s.HandleFunc("/check", DatabasePingHandler). 19 | Methods("GET") 20 | } 21 | 22 | // PingHandler returns 200 OK response with version of the service in the body 23 | func PingHandler(w http.ResponseWriter, r *http.Request) { 24 | w.WriteHeader(200) 25 | w.Write([]byte(datastore.Environ.Config.Version)) 26 | } 27 | 28 | // DatabasePingHandler will return a json data with 29 | // 200: { "database": "OK" } or 30 | // 500: { "database": "dial tcp 127.0.0.1:5432: connect: connection refused" } 31 | func DatabasePingHandler(w http.ResponseWriter, r *http.Request) { 32 | w.Header().Set("Content-Type", response.JSONHeader) 33 | status := "OK" 34 | 35 | err := datastore.Environ.DB.HealthCheck() 36 | if err != nil { 37 | status = err.Error() 38 | w.WriteHeader(500) 39 | } 40 | 41 | json.NewEncoder(w).Encode(map[string]string{"database": status}) 42 | } 43 | -------------------------------------------------------------------------------- /service/status/handler_test.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "net/http/httptest" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/CanonicalLtd/serial-vault/config" 9 | "github.com/CanonicalLtd/serial-vault/datastore" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | const version = "1.2.3" 14 | 15 | func TestAddStatusEndpointsPing(t *testing.T) { 16 | // Mock the database 17 | config := config.Settings{Version: version} 18 | datastore.Environ = &datastore.Env{DB: &datastore.MockDB{}, Config: config} 19 | 20 | // run the test 21 | router := mux.NewRouter() 22 | AddStatusEndpoints("/_status", router) 23 | 24 | w := httptest.NewRecorder() 25 | r := httptest.NewRequest("GET", "/_status/ping", nil) 26 | 27 | router.ServeHTTP(w, r) 28 | 29 | if w.Code != 200 { 30 | t.Errorf("expected code 200, got %d", w.Code) 31 | } 32 | if w.Body.String() != version { 33 | t.Errorf("expected body %s, got %s", version, w.Body.String()) 34 | } 35 | } 36 | 37 | func TestAddStatusEndpointsDBPingError(t *testing.T) { 38 | // Mock the database 39 | config := config.Settings{Version: version} 40 | datastore.Environ = &datastore.Env{DB: &datastore.MockDB{}, Config: config} 41 | datastore.Environ.DB = &datastore.ErrorMockDB{} 42 | 43 | // run the test 44 | router := mux.NewRouter() 45 | AddStatusEndpoints("/_status", router) 46 | 47 | w := httptest.NewRecorder() 48 | r := httptest.NewRequest("GET", "/_status/check", nil) 49 | 50 | router.ServeHTTP(w, r) 51 | 52 | if w.Code != 500 { 53 | t.Errorf("expected code 500, got %d", w.Code) 54 | } 55 | 56 | expected := `{"database":"Health check failed"}` 57 | got := strings.TrimSpace(w.Body.String()) 58 | if expected != got { 59 | t.Errorf("expected body %s, got %s", expected, got) 60 | } 61 | } 62 | 63 | func TestAddStatusEndpointsDBPingOK(t *testing.T) { 64 | // Mock the database 65 | config := config.Settings{Version: version} 66 | datastore.Environ = &datastore.Env{DB: &datastore.MockDB{}, Config: config} 67 | datastore.Environ.DB = &datastore.MockDB{} 68 | 69 | // run the test 70 | router := mux.NewRouter() 71 | AddStatusEndpoints("/_status", router) 72 | 73 | w := httptest.NewRecorder() 74 | r := httptest.NewRequest("GET", "/_status/check", nil) 75 | 76 | router.ServeHTTP(w, r) 77 | 78 | if w.Code != 200 { 79 | t.Errorf("expected code 200, got %d", w.Code) 80 | } 81 | 82 | expected := `{"database":"OK"}` 83 | got := strings.TrimSpace(w.Body.String()) 84 | if expected != got { 85 | t.Errorf("expected body %s, got %s", expected, got) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /service/store/handlers_store.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package store 21 | 22 | import ( 23 | "encoding/json" 24 | "net/http" 25 | 26 | "github.com/CanonicalLtd/serial-vault/service/log" 27 | 28 | "github.com/CanonicalLtd/serial-vault/service/auth" 29 | "github.com/CanonicalLtd/serial-vault/service/response" 30 | "github.com/CanonicalLtd/serial-vault/store" 31 | ) 32 | 33 | const ( 34 | ssoBaseURL = "https://login.ubuntu.com/api/v2/" 35 | storeBaseURL = "https://dashboard.snapcraft.io/dev/api/" 36 | ) 37 | 38 | // Permissions is the SSO authorization for the store 39 | type Permissions struct { 40 | Permissions []string `json:"permissions"` 41 | } 42 | 43 | // ACL is the SSO authorization for the store 44 | type ACL struct { 45 | Macaroon string `json:"macaroon"` 46 | } 47 | 48 | // Auth is the SSO authorization for the store 49 | type Auth struct { 50 | Email string `json:"email"` 51 | Password string `json:"password"` 52 | OTP string `json:"otp"` 53 | } 54 | 55 | // KeyRegister is the API method to upload a signing-key to the store 56 | func KeyRegister(w http.ResponseWriter, r *http.Request) { 57 | authUser, err := auth.GetUserFromJWT(w, r) 58 | if err != nil { 59 | response.FormatStandardResponse(false, "error-auth", "", err.Error(), w) 60 | return 61 | } 62 | 63 | // Decode the JSON body 64 | keyAuth := store.KeyRegister{} 65 | err = json.NewDecoder(r.Body).Decode(&keyAuth) 66 | if err != nil { 67 | log.Printf("Error in store key request: %v", err) 68 | response.FormatStandardResponse(false, "error-decode-json", "", "", w) 69 | return 70 | } 71 | 72 | keyRegisterHandler(w, authUser, false, keyAuth) 73 | } 74 | -------------------------------------------------------------------------------- /settings.yaml.example: -------------------------------------------------------------------------------- 1 | title: "Serial Vault" 2 | logo: "/static/images/logo-ubuntu-white.svg" 3 | 4 | # Service mode: signing or admin 5 | mode: signing 6 | 7 | # Http ports for api and admin mode: defaults are 8081 for admin mode and 8080 for signing mode 8 | # You need to set both only if you want to run both services using the same settings.yaml file 9 | portAdmin: 8081 10 | portSigning: 8080 11 | 12 | # Path to the assets (${docRoot}/static) 13 | docRoot: "." 14 | 15 | # Backend database details 16 | driver: "postgres" 17 | datasource: "dbname=serialvault sslmode=disable" 18 | 19 | # Signing Key Store 20 | #keystore: "filesystem" 21 | #keystorePath: "./keystore" 22 | 23 | # For Database 24 | keystore: "database" 25 | # CHANGEME: This keystoreSecret value is only a sample. Please provide another custom generated one 26 | keystoreSecret: "secret code to encrypt the auth-key hash" 27 | 28 | # For TPM 2.0 29 | #keystore: "tpm2.0" 30 | #keystorePath: "./keystore" 31 | #keystoreSecret: "this needs to be 32 bytes long!!" 32 | 33 | # 32 bytes long key to protect server from cross site request forgery attacks 34 | # CHANGEME: This csrfAuthKey value is only a sample. Please provide another custom generated one 35 | csrfAuthKey: "2E6ZYnVYUfDLRLV/ne8M6v1jyB/376BL9ORnN3Kgb04uSFalr2ygReVsOt0PaGEIRuID10TePBje5xdjIOEjQQ==" 36 | 37 | # Return URL of the service (needed for OpenID) 38 | urlHost: "serial-vault:8081" 39 | urlScheme: http 40 | 41 | # Enable user authentication using Ubuntu SSO 42 | enableUserAuth: True 43 | 44 | # Key needed for USSO. 45 | # CHANGEME: This jwtSecret is only a sample. Please provide another custom generated value 46 | jwtSecret: "regoo7Koh7Jeij2hig0Kaeg1ait0eeghaew7Ogheey4pheejohyaongoh6thoBeech6ahc9yaWo3ef4Dah3heeguoqu0oa9A" 47 | 48 | # Factory sync only 49 | syncUrl: "https://serial-vault-partners.canonical.com/api/" 50 | syncUser: "lpuser" 51 | syncAPIKey: "user-apikey" 52 | -------------------------------------------------------------------------------- /setup-container: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # Barf if not uid 0 6 | if [ $(id -u) -ne "0" ]; then 7 | echo "Please run me with sudo" 8 | exit 9 | fi 10 | 11 | if ! grep -qa container=lxc /proc/1/environ; then 12 | echo "You're not in an LXC container, so I'm refusing to run." 13 | exit 14 | fi 15 | 16 | # Disable debconf questions. 17 | export DEBIAN_FRONTEND=noninteractive 18 | 19 | # If your host system is not in English, need to fix the locale for 20 | # the lxc, as the project (and its tests) assume an English machine. 21 | sed -i 's/LANG=.*/LC_ALL="en_US.UTF-8"/' /etc/default/locale 22 | 23 | # install classic snaps, use ols-vms.conf as the source 24 | for snap_ in $( grep sideload.classic_snaps ols-vms.conf | cut -d '=' -f 2 | xargs | tr " " "\n" | cut -d_ -f1 ) ; do 25 | snap install ${snap_} --classic 26 | done 27 | 28 | # install strictly confined snaps 29 | for snap_ in $(grep 'sideload.snaps\s*=' ols-vms.conf | cut -d '=' -f 2 | xargs | tr " " "\n" | cut -d_ -f1) ; do 30 | snap install ${snap_} 31 | done 32 | 33 | # Let's make sure your system is up to date 34 | apt-get update 35 | apt-get upgrade -y --allow-downgrades --allow-remove-essential --allow-change-held-packages 36 | apt-get autoremove -y 37 | 38 | # Install project's OS dependencies. 39 | cat dependencies.txt dependencies-devel.txt | xargs apt install --no-install-recommends -y 40 | 41 | # we are done 42 | echo All done, now you can do 43 | echo make bootstrap 44 | 45 | -------------------------------------------------------------------------------- /snap/plugins/x-apache.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import snapcraft 3 | 4 | 5 | class ApachePlugin(snapcraft.BasePlugin): 6 | 7 | @classmethod 8 | def schema(cls): 9 | schema = super().schema() 10 | 11 | schema['properties']['modules'] = { 12 | 'type': 'array', 13 | 'minitems': 1, 14 | 'uniqueItems': True, 15 | 'items': { 16 | 'type': 'string' 17 | }, 18 | } 19 | 20 | schema['properties']['mpm'] = { 21 | 'type': 'string', 22 | 'default': 'event', 23 | } 24 | 25 | schema['required'].append('modules') 26 | 27 | return schema 28 | 29 | def __init__(self, name, options, project): 30 | super().__init__(name, options, project) 31 | 32 | self.build_packages.extend( 33 | ['pkg-config', 'libapr1-dev', 'libaprutil1-dev', 'libpcre3-dev', 34 | 'libssl-dev']) 35 | 36 | def build(self): 37 | super().build() 38 | 39 | subprocess.check_call( 40 | "./configure --prefix={} --with-mpm={} --enable-modules=none --enable-mods-shared='{}' ENABLED_DSO_MODULES='{}'".format( 41 | self.installdir, self.options.mpm, 42 | ' '.join(self.options.modules), 43 | ','.join(self.options.modules)), 44 | cwd=self.builddir, shell=True) 45 | 46 | self.run( 47 | ['make', '-j{}'.format( 48 | self.project.parallel_build_count)], 49 | cwd=self.builddir) 50 | self.run(['make', 'install'], cwd=self.builddir) -------------------------------------------------------------------------------- /spread.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017 Canonical Ltd 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License version 3 as 6 | # published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | 17 | project: serial-vault 18 | 19 | environment: 20 | PROJECT_PATH: /home/serial-vault 21 | TESTSLIB: $PROJECT_PATH/tests/lib 22 | SNAP_NAME: factory-serial-vault 23 | SNAP_ARCH: amd64 24 | # Allow the host to pass the channel to use for the test run 25 | SNAP_CHANNEL: $(HOST:echo $SNAP_CHANNEL) 26 | 27 | backends: 28 | qemu: 29 | systems: 30 | - ubuntu-core-16: 31 | username: test 32 | password: test 33 | 34 | # Put this somewhere where we have read-write access 35 | path: /home/serial-vault 36 | 37 | exclude: 38 | - .git 39 | 40 | prepare: | 41 | . $TESTSLIB/prepare-all.sh 42 | 43 | prepare-each: | 44 | # Cleanup logs so we can just dump what has happened in the debug-each 45 | # step below after a test case ran. 46 | journalctl --rotate 47 | sleep .1 48 | journalctl --vacuum-time=1ms 49 | dmesg -c > /dev/null 50 | 51 | debug-each: | 52 | journalctl 53 | dmesg | grep DENIED || true 54 | 55 | suites: 56 | tests/main/: 57 | summary: Full-system tests for Serial Vault 58 | prepare: | 59 | . $TESTSLIB/prepare.sh 60 | restore-each: | 61 | . $TESTSLIB/restore-each.sh -------------------------------------------------------------------------------- /static/app.html: -------------------------------------------------------------------------------- 1 | {{.Title}}
-------------------------------------------------------------------------------- /static/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /static/images/checkbox_checked_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/images/checkbox_checked_16.png -------------------------------------------------------------------------------- /static/images/checkbox_unchecked_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/images/checkbox_unchecked_16.png -------------------------------------------------------------------------------- /static/images/chevron-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/images/chevron-down.png -------------------------------------------------------------------------------- /static/images/chevron-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/images/chevron-up.png -------------------------------------------------------------------------------- /static/images/navigation-menu-plain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/svg+xml 4 | -------------------------------------------------------------------------------- /static/images/serial-vault-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/static/images/serial-vault-logo.png -------------------------------------------------------------------------------- /static/js/2.e900bc10.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** @license React v0.18.0 8 | * scheduler.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | /** @license React v16.12.0 17 | * react-dom.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v16.12.0 26 | * react-is.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v16.12.0 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /static/js/runtime-main.c685bd3b.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,a,p=r[0],i=r[1],l=r[2],c=0,s=[];c. 17 | * 18 | */ 19 | 20 | package sync 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/CanonicalLtd/serial-vault/manage" 26 | ) 27 | 28 | // DatabaseCommand is the main command for database management 29 | type DatabaseCommand struct{} 30 | 31 | // Execute the database schema updates 32 | func (cmd DatabaseCommand) Execute(args []string) error { 33 | fmt.Println("Update the database schema...") 34 | 35 | openDatabase() 36 | 37 | manage.UpdateDatabase() 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /sync/sync.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package sync 21 | 22 | import ( 23 | "github.com/CanonicalLtd/serial-vault/config" 24 | "github.com/CanonicalLtd/serial-vault/datastore" 25 | "github.com/CanonicalLtd/serial-vault/service/log" 26 | ) 27 | 28 | // Command defines the options for the serial-vault-admin command-line utility 29 | type Command struct { 30 | SettingsFile string `short:"c" long:"config" description:"Path to the config file" default:"./settings.yaml"` 31 | Start StartCommand `command:"sync" alias:"s" description:"Start the factory sync process"` 32 | Database DatabaseCommand `command:"database" alias:"d" description:"Database schema update"` 33 | } 34 | 35 | // Sync is the implementation of the command configuration for the serial-vault-admin command-line 36 | var Sync Command 37 | 38 | func openDatabase() { 39 | // Check that the database has not been set e.g. by a mock 40 | if datastore.Environ.DB != nil { 41 | return 42 | } 43 | 44 | log.Infof("Open the settings file: %s", Sync.SettingsFile) 45 | config.ReadConfig(&datastore.Environ.Config, Sync.SettingsFile) 46 | 47 | // Open the connection to the database 48 | datastore.OpenSysDatabase(datastore.Environ.Config.Driver, datastore.Environ.Config.DataSource) 49 | } 50 | -------------------------------------------------------------------------------- /sync/sync_test.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package sync_test 21 | 22 | import ( 23 | "os" 24 | "testing" 25 | 26 | "github.com/CanonicalLtd/serial-vault/sync" 27 | "github.com/jessevdk/go-flags" 28 | 29 | "gopkg.in/check.v1" 30 | ) 31 | 32 | // Hook up check.v1 into the "go test" runner 33 | func Test(t *testing.T) { check.TestingT(t) } 34 | 35 | type suiteTest struct { 36 | Args []string 37 | ErrorMessage string 38 | MockErrorDB bool 39 | MockFail bool 40 | } 41 | 42 | func mockArgs(args ...string) (restore func()) { 43 | old := os.Args 44 | os.Args = args 45 | return func() { os.Args = old } 46 | } 47 | 48 | func RunMain() error { 49 | // Parse the command line arguments and execute the command 50 | parser := flags.NewParser(&sync.Sync, flags.HelpFlag) 51 | _, err := parser.Parse() 52 | return err 53 | } 54 | 55 | func runTest(c *check.C, args []string, errorMessage string) { 56 | restore := mockArgs(args...) 57 | defer restore() 58 | 59 | err := RunMain() 60 | 61 | if len(errorMessage) == 0 { 62 | c.Check(err, check.IsNil) 63 | } else { 64 | c.Assert(err, check.ErrorMatches, errorMessage) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/authorized_keys: -------------------------------------------------------------------------------- 1 | rsa-ssh AAAAB3NzaC1yc2EAAAADAQABAAABAQC2209e7581b7d0209150a7e0c071ec0e0b9ad58515c573e2501edd0e5b170d0e780957c5d5db70b02ab09ee0d0209e7a37aa258bab0e757ad75757ce75a78b517 Fake Key 2 | rsh-ssh AAAAB3NzaC1yc2EAAAADAQABAAABAQD00109bdeee9c50a0507b22a7c02ab09517515b0a258b58eeeac0770d0c071e8ba0Db51ab0e59b9ed0209579e89e4e7de95057c02b80a0ece250e11ec522b7d02 Not real 3 | rsa-ssh AAAAB3NzaC1yc2EAAAADAQABAAABAQC2D009eebfb35a07b22a8a95a0b9Eece80eb915700ccaeca0cb85da0a07078905de701b7057cb28a8b50ff5c5ade1e9b70702250a7575de102ab09b7 Totally unlegit 4 | -------------------------------------------------------------------------------- /tests/lib/prepare-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # We don't have to build a snap when we should use one from a 4 | # channel 5 | if [ -n "$SNAP_CHANNEL" ] ; then 6 | exit 0 7 | fi 8 | 9 | # If there is a serial-vault snap prebuilt for us, lets take 10 | # that one to speed things up. 11 | if [ -e ${PROJECT_PATH}/${SNAP_NAME}_*_${SNAP_ARCH}.snap ] ; then 12 | exit 0 13 | fi 14 | 15 | # Setup classic snap and build the wifi-connect snap in there 16 | snap install --devmode --beta classic 17 | 18 | cat <<-EOF > /home/test/build-snap.sh 19 | #!/bin/sh 20 | set -ex 21 | apt update 22 | apt install -y --force-yes snapcraft 23 | apt install -y --force-yes git 24 | cd ${PROJECT_PATH} 25 | snapcraft clean 26 | snapcraft 27 | EOF 28 | chmod +x /home/test/build-snap.sh 29 | sudo classic /home/test/build-snap.sh 30 | snap remove classic 31 | 32 | # Make sure we have a snap build 33 | test -e ${PROJECT_PATH}/${SNAP_NAME}_*_${SNAP_ARCH}.snap 34 | -------------------------------------------------------------------------------- /tests/lib/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . $TESTSLIB/utilities.sh 3 | 4 | echo "Wait for firstboot change to be ready" 5 | while ! snap changes | grep -q "Done"; do 6 | snap changes || true 7 | snap change 1 || true 8 | sleep 1 9 | done 10 | 11 | echo "Ensure fundamental snaps are still present" 12 | . $TESTSLIB/snap-names.sh 13 | for name in $gadget_name $kernel_name $core_name; do 14 | if ! snap list | grep -q $name ; then 15 | echo "Not all fundamental snaps are available, all-snap image not valid" 16 | echo "Currently installed snaps:" 17 | snap list 18 | exit 1 19 | fi 20 | done 21 | 22 | echo "Kernel has a store revision" 23 | snap list | grep ^${kernel_name} | grep -E " [0-9]+\s+canonical" 24 | 25 | # Snapshot of the current snapd state for a later restore 26 | if [ ! -f $SPREAD_PATH/snapd-state.tar.gz ] ; then 27 | systemctl stop snapd.service snapd.socket 28 | tar czf $SPREAD_PATH/snapd-state.tar.gz /var/lib/snapd /etc/netplan 29 | systemctl start snapd.socket 30 | fi 31 | 32 | snap_install $SNAP_NAME 33 | 34 | # For debugging dump all snaps and connected slots/plugs 35 | snap list 36 | snap interfaces 37 | -------------------------------------------------------------------------------- /tests/lib/restore-each.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . $TESTSLIB/snap-names.sh 4 | . $TESTSLIB/utilities.sh 5 | 6 | # Remove all snaps not being the core, gadget, kernel or snap we're testing 7 | for snap in /snap/*; do 8 | snap="${snap:6}" 9 | case "$snap" in 10 | "bin" | "$gadget_name" | "$kernel_name" | "$core_name" | "$SNAP_NAME" ) 11 | ;; 12 | *) 13 | snap remove "$snap" 14 | ;; 15 | esac 16 | done -------------------------------------------------------------------------------- /tests/lib/snap-names.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | gadget_name=$(snap list | sed -n 's/^\(pc\|pi[23]\|dragonboard\) .*/\1/p') 3 | kernel_name=$gadget_name-kernel 4 | core_name=$(snap list | awk '/^(ubuntu-)?core / {print $1; exit}') 5 | if [ "$kernel_name" = "pi3-kernel" ] ; then 6 | kernel_name=pi2-kernel 7 | fi 8 | -------------------------------------------------------------------------------- /tests/main/documentation-builds/task.yaml: -------------------------------------------------------------------------------- 1 | summary: Verify the project documentation is building without errors 2 | 3 | execute: | 4 | # Need to install in devmode as otherwise the snap can't access our project 5 | # home which is outside of the home directory of our current user. 6 | snap install --devmode documentation-builder 7 | 8 | outdir=$PROJECT_PATH/docs/build 9 | 10 | cd $PROJECT_PATH/docs 11 | /snap/bin/documentation-builder --output-path $outdir 12 | 13 | # Ensure we have some files in the output directory 14 | test `find $outdir -type f | wc -l` -gt 0 15 | -------------------------------------------------------------------------------- /tests/main/installation/task.yaml: -------------------------------------------------------------------------------- 1 | summary: Test factory-serial-vault snap installation was successful 2 | 3 | execute: | 4 | . $TESTSLIB/utilities.sh 5 | 6 | # Ensure all necessary plugs/slots are connected 7 | snap interfaces | grep -Pzq ":network-bind +[a-z,-]*serial-vault" 8 | snap interfaces | grep -Pzq ":network +[a-z,-]*serial-vault" 9 | -------------------------------------------------------------------------------- /usso/constants.go: -------------------------------------------------------------------------------- 1 | // -*- Mode: Go; indent-tabs-mode: t -*- 2 | 3 | /* 4 | * Copyright (C) 2017-2018 Canonical Ltd 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 3 as 8 | * published by the Free Software Foundation. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | package usso 21 | 22 | const jwtSecret = "TODO-ReplaceWithASecretFromTheConfigurationFile" 23 | 24 | // ClaimsKey is the context key for the JWT claims 25 | var ClaimsKey struct{} 26 | 27 | // UserClaims holds the JWT custom claims for a user 28 | const ( 29 | ClaimsIdentity = "identity" 30 | ClaimsUsername = "username" 31 | ClaimsEmail = "email" 32 | ClaimsName = "name" 33 | ClaimsRole = "role" 34 | StandardClaimExpiresAt = "exp" 35 | ) 36 | 37 | // JWTCookie is the name of the cookie used to store the JWT 38 | const JWTCookie = "X-Auth-Token" 39 | -------------------------------------------------------------------------------- /webapp-admin/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | *.css 24 | 25 | junit.xml -------------------------------------------------------------------------------- /webapp-admin/README.md: -------------------------------------------------------------------------------- 1 | # Admin Web App 2 | 3 | ## Overview 4 | The Admin Service webapp is built on React.js and uses the current LTS version of Node.js. 5 | 6 | ## Pre-requisites 7 | - Install NVM 8 | Install the [Node Version Manager](https://github.com/creationix/nvm) that will allow a specific 9 | version of Node.js to be installed. Follow the installation instructions. 10 | 11 | - Install the latest stable Node.js and npm 12 | The latest stable (LTS) version of Node can be found on the [Node website](nodejs.org). 13 | ```bash 14 | # Overview of available commands 15 | nvm help 16 | 17 | # Install the latest stable version 18 | nvm install lts/* 19 | 20 | # Select the version to use 21 | nvm ls 22 | nvm use lts/* 23 | ``` 24 | 25 | - Install the nodejs dependencies 26 | ```bash 27 | cd webapp-admin # Make sure you are in the webapp directory 28 | npm install 29 | ``` 30 | 31 | ## Testing 32 | To run the tests for an app: 33 | ```bash 34 | make test-frontend 35 | ``` 36 | 37 | ## Building 38 | To run a full build: 39 | ```bash 40 | build-frontend 41 | ``` 42 | 43 | This runs the build, which creates the files in the ./build directory and then copies the 44 | relevant files to the static directories. 45 | 46 | Create React App also has a handy development mode that loads the view in the browser and 47 | refreshes it on every save - no extra build step required: 48 | ```bash 49 | cd webapp-admin # Make sure you are in the webapp-user directory 50 | npm start 51 | ``` 52 | The dev mode launches ./public/index.html on port :3000. 53 | -------------------------------------------------------------------------------- /webapp-admin/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Build the project 4 | npm run build 5 | 6 | # Copy the main assets to the static directory 7 | cp -R build/static/js/*.js ../static/js/bundle.js 8 | cp -R build/static/css/*.css ../static/css/application.css 9 | cp -R build/static/css/*.css.map ../static/css/application.css.map 10 | 11 | # cleanup 12 | rm -rf ./build 13 | -------------------------------------------------------------------------------- /webapp-admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webapp-admin", 3 | "version": "0.4.1", 4 | "private": true, 5 | "dependencies": { 6 | "history": "^4.10.1", 7 | "jwt-decode": "^2.2.0", 8 | "moment": "^2.24.0", 9 | "npm": "^6.14.6", 10 | "react": "^16.12.0", 11 | "react-intl": "^3.11.0", 12 | "react-scripts": "^3.3.1", 13 | "then-request": "^6.0.2" 14 | }, 15 | "scripts": { 16 | "build-css": "node-sass src/ -o src/", 17 | "start": "react-scripts start", 18 | "build": "npm run build-css && react-scripts build", 19 | "test": "npm run build-css && react-scripts test --env=jsdom", 20 | "test:ci": "npm run build-css && react-scripts test --env=jsdom --no-cache --reporters=jest-junit --bail --ci --coverage", 21 | "eject": "react-scripts eject" 22 | }, 23 | "devDependencies": { 24 | "enzyme": "^3.11.0", 25 | "enzyme-adapter-react-16": "^1.15.2", 26 | "jest-junit": "^10.0.0", 27 | "node-sass": "^4.12.0", 28 | "react-dom": "^16.12.0", 29 | "vanilla-framework": "^1.7.1" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /webapp-admin/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/webapp-admin/public/favicon.ico -------------------------------------------------------------------------------- /webapp-admin/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{.Title}} 8 | 9 | 10 | 11 | 12 |
13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /webapp-admin/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /webapp-admin/public/static/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{.Title}} 8 | 9 | 10 | 11 | 12 | 13 |
14 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /webapp-admin/public/static/app_user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{.Title}} 8 | 9 | 10 | 11 | 12 | 13 |
14 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /webapp-admin/public/static/images/checkbox_checked_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/webapp-admin/public/static/images/checkbox_checked_16.png -------------------------------------------------------------------------------- /webapp-admin/public/static/images/checkbox_unchecked_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/webapp-admin/public/static/images/checkbox_unchecked_16.png -------------------------------------------------------------------------------- /webapp-admin/public/static/images/chevron-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/webapp-admin/public/static/images/chevron-down.png -------------------------------------------------------------------------------- /webapp-admin/public/static/images/chevron-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/serial-vault/f5ad8eaf6a5dcc66068db430b6561dd628307ca2/webapp-admin/public/static/images/chevron-up.png -------------------------------------------------------------------------------- /webapp-admin/public/static/images/navigation-menu-plain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/svg+xml 4 | -------------------------------------------------------------------------------- /webapp-admin/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import Messages from './components/messages'; 5 | import {IntlProvider} from 'react-intl'; 6 | 7 | // Mock the AppState method for locale 8 | window.AppState = {getLocale: function() {return 'en'}}; 9 | 10 | const token = { role: 200, name: 'Steven Vault' } 11 | const tokenUser = { role: 100, name: 'Steven Vault' } 12 | 13 | it('renders without crashing', () => { 14 | const div = document.createElement('div'); 15 | ReactDOM.render(( 16 | 17 | 18 | 19 | ), div); 20 | }); 21 | -------------------------------------------------------------------------------- /webapp-admin/src/__tests__/alertbox-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2017 Canonical Ltd 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 3 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | * 16 | */ 17 | 'use strict' 18 | 19 | import React from 'react'; 20 | import ReactDOM from 'react-dom'; 21 | import ReactTestUtils from 'react-dom/test-utils'; 22 | import AlertBox from '../components/AlertBox'; 23 | var Messages = require('../components/messages').en; 24 | 25 | jest.dontMock('../components/AlertBox'); 26 | 27 | 28 | describe('alert box', function() { 29 | it('displays the alert box with a message', function() { 30 | 31 | var handleYesClick = jest.fn(); 32 | var handleNoClick = jest.fn(); 33 | 34 | // Render the component 35 | var page = ReactTestUtils.renderIntoDocument( 36 | 37 | ); 38 | 39 | expect(ReactTestUtils.isCompositeComponent(page)).toBeTruthy(); 40 | 41 | // Check all the expected elements are rendered 42 | var div = ReactTestUtils.findRenderedDOMComponentWithTag(page, 'div'); 43 | var p = ReactTestUtils.findRenderedDOMComponentWithTag(page, 'p'); 44 | expect(p.textContent).toBe('The message goes here'); 45 | 46 | }); 47 | 48 | it('displays no box when there is no message', function() { 49 | 50 | // Render the component 51 | var page = ReactTestUtils.renderIntoDocument( 52 | 53 | ); 54 | 55 | expect(ReactTestUtils.isCompositeComponent(page)).toBeTruthy(); 56 | var span = ReactTestUtils.scryRenderedDOMComponentsWithTag(page, 'span'); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /webapp-admin/src/__tests__/footer-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2017 Canonical Ltd 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 3 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | * 16 | */ 17 | 'use strict' 18 | 19 | import React from 'react'; 20 | import ReactDOM from 'react-dom'; 21 | import ReactTestUtils from 'react-dom/test-utils'; 22 | import { createRenderer } from 'react-test-renderer/shallow'; 23 | import Footer from '../components/Footer'; 24 | 25 | jest.dontMock('../components/Footer'); 26 | jest.dontMock('../components/Utils'); 27 | 28 | describe('footer', function() { 29 | it('displays the footer', function() { 30 | var Messages = require('../components/messages').en; 31 | 32 | // Shallow render the component 33 | var shallowRenderer = createRenderer(); 34 | 35 | // Mock the data retrieval from the API 36 | var getVersion = jest.fn(); 37 | Footer.prototype.getVersion = getVersion; 38 | window.AppState = {getLocale: function() {return 'en'}}; 39 | 40 | shallowRenderer.render( 41 |