Hi I am having private docker registry with token based auth where I am able to do the following:
- Authenticate & Login to my private registry from command line via docker login successfully.
- Push images to private registry
- Pull images from the private registry
I am facing issues when I try to achieve the following tasks:
- List all the private registry images
- Show details of specific docker image
- Show tags of specific docker image
Docker Registry - registry.my-domain.com NodeJs/ExpressJs APIs - testapi.api-domain.com
Following is my complete code for Docker Registry in NodeJs / ExpressJs
require("dotenv").config();
const fs = require("fs");
const libtrust = require("libtrust");
const express = require("express");
const jwt = require("jsonwebtoken");
const winston = require("winston");
const { compare } = require("../../helpers/bcrypt.helper.js");
const Route = express.Router();
const { Developer } = require("../../models/DeveloperModel.js");
const { findByEmail } = require("../../services/developer.service.js");
const { dockerLoginValidation } = require("../../validations/auth.validation.js");
//Docker registry token options
function createTokenOptions(request) {
const options = {};
options.headers = request.headers;
options.account = request.query.account;
options.service = request.query.service;
/* Issuer value must match the value configured via docker-compose */
options.issuer = "registry.my-domain.com";
const parts = request.query.scope && request.query.scope.split(":");
if (parts && parts.length > 0) {
options.type = parts[0]; // repository
}
if (parts && parts.length > 1) {
options.name = parts[1]; // foo/repoName
}
if (parts && parts.length > 2) {
options.actions = parts[2].split(","); // requested actions
}
return options;
}
/** For time being I am give pull & push permission to everyone */
async function authorize(options) {
const { account } = options;
//Mongodb lookup Repository from Developer collection and check if the user has access to the repository
const developer = await Developer.findOne({ email: account });
/** If unauthorized access then only pull permissions is granted */
return ["pull", "push"];
}
//Docker registry create token
function createToken(options, actions) {
const { account, service, issuer, type, name } = options;
const publicKey = fs.readFileSync("/etc/letsencrypt/live/registry.my-domain.com/fullchain.pem", { encoding: "utf8" });
const privateKey = fs.readFileSync("/etc/letsencrypt/live/registry.my-domain.com/privkey.pem", { encoding: "utf8" });
const keyId = libtrust.fingerprint(publicKey);
const headers = {
alg: "RS256",
typ: "JWT",
kid: keyId,
};
const claimSet = {
iss: issuer,
sub: account,
aud: service,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // 30 days
nbf: Math.floor(Date.now() / 1000) - 10,
iat: Math.floor(Date.now() / 1000),
jti: Math.random().toString(36).substring(7),
password: "admin",
// jwtid: Math.random().toString(36).substring(7),
access: [
/* {
type: type,
name: name,
actions: actions,
}, */
{
type: type,
name: "*",
actions: ["pull", "push"],
},
],
};
return jwt.sign(claimSet, privateKey, { algorithm: "RS256", keyid: keyId, header: headers });
}
//TODO: Login attempts limit to x
Route.get("/login", async (request, response) => {
// Extract the base64 encoded string from the authorization header
const authorizationString = request.headers.authorization.split(" ")[1];
// Decode the base64 encoded string
const credentialsString = Buffer.from(authorizationString, "base64").toString();
// Split the decoded string by the colon (:) character to get the username and password
const [username, password] = credentialsString.split(":");
const errors = dockerLoginValidation({
username: username,
password: password,
});
if (errors.length > 0) {
return response.status(401).json({
status: "error",
message: "Invalid email or password.",
errors: [
{
message: "Invalid email or password",
field: "email",
},
],
});
}
const user = await findByEmail(username);
if (!user) {
return response.status(401).json({
status: "error",
message: "Invalid email or password.",
errors: [
{
message: "Invalid email or password",
field: "email",
},
],
});
}
if (!(await compare(password, user.password))) {
return response.status(401).json({
status: "error",
message: "Invalid email or password.",
errors: [
{
message: "Invalid email or password",
field: "email",
},
],
});
}
const tokenOptions = createTokenOptions(request);
const actions = await authorize(tokenOptions);
const userJWTToken = createToken(tokenOptions, actions);
await user.updateOne({
$push: {
tokens: {
token: userJWTToken,
expiration_at: new Date().setSeconds(process.env.JWT_EXPIRATION_SECONDS),
},
},
});
return response.status(200).send({
status: "success",
message: "Authenticated user successfully.",
token: userJWTToken,
});
});
module.exports = Route;
The following is my docker-compose.yml
version: "3.9"
services:
registry:
container_name: registry
image: registry:2.8.1
environment:
REGISTRY_HTTP_SECRET: 134452-9a3f-4c73-bf5e-e0864b400bc8
#S3 bucket as docker storage
REGISTRY_STORAGE: s3
REGISTRY_STORAGE_S3_ACCESSKEY: 13I42L7IH5TIV6GQIAUV
REGISTRY_STORAGE_S3_SECRETKEY: 6qu15BpbaizVe0+KQ9LvUPCU4STG9Ez1c1hneOXS
REGISTRY_STORAGE_S3_REGION: us-east-1
REGISTRY_STORAGE_S3_BUCKET: my-domain-docker-registry
REGISTRY_STORAGE_DELETE_ENABLED: false
#Docker token based authentication
REGISTRY_AUTH: token
REGISTRY_AUTH_TOKEN_REALM: "https://testapi.my-domain.com/api/docker/auth/login"
REGISTRY_AUTH_TOKEN_SERVICE: "Authentication"
REGISTRY_AUTH_TOKEN_ISSUER: "registry.my-domain.com"
#Letsencrupt certificate
REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE: "/certs/live/registry.my-domain.com/fullchain.pem"
REGISTRY_HTTP_TLS_CERTIFICATE: "/certs/live/registry.my-domain.com/fullchain.pem"
REGISTRY_HTTP_TLS_KEY: "/certs/live/registry.my-domain.com/privkey.pem"
# REGISTRY_AUTH_TOKEN_AUTOREDIRECT: false
ports:
- 5000:5000
restart: always
volumes:
- "/etc/letsencrypt:/certs"
When I try to list the docker images I keep getting the following error even after passing the
Authorization: Bearer TOKEN
Following is the error in the Postman