搭建可认证的docker registry

1. 生成rsa 配对的公钥私钥

openssl genrsa -out ./private_key.pem 4096
openssl req -new -x509 -key ./private_key.pem -out ./root.crt -days 3650 -subj /C=CN/ST=state/L=CN/O=cloverstd/OU=cloverstd\ unit/CN=hui.lu/emailAddress=xingyue@staff.sina.com.cn

2. pull registry v2

docker pull registry:2.1.1

3. 修改配置文件config.yml

version: 0.1
log:
  level: debug
  fields:
    service: registry
    environment: development
storage:
    cache:
        layerinfo: inmemory
    filesystem:
        rootdirectory: /tmp/registry-dev
    maintenance:
        uploadpurging:
            enabled: false
http:
    addr: :5000
    secret: asecretforlocaldevelopment
    debug:
        addr: localhost:5001

auth:  
    token: 
        issuer: registry-token-issuer
        realm: http://192.168.122.186:8080/service/token
        rootcertbundle: /etc/registry/root.crt
        service: token-service

redis:
  addr: localhost:6379
  pool:
    maxidle: 16
    maxactive: 64
    idletimeout: 300s
  dialtimeout: 10ms
  readtimeout: 10ms
  writetimeout: 10ms
notifications:
    endpoints:
        - name: local-8082
          url: http://localhost:5003/callback
          headers:
             Authorization: [Bearer <an example token>]
          timeout: 1s
          threshold: 10
          backoff: 1s
          disabled: true
        - name: local-8083
          url: http://localhost:8083/callback
          timeout: 1s
          threshold: 10
          backoff: 1s
          disabled: true
   

4. 重新编译生成新的auth_registy:

FROM docker.io/registry:2.0
COPY config.yml /go/src/github.com/docker/distribution/cmd/registry/config.yml

5. 启动docker registry
[/shell]
docker run -d -p 5000:5000 -v /opt/registry:/var/lib/registry -v `pwd`/root.crt:/etc/registry/root.crt:ro auth_registry
[/shell]
6.添加认证的python脚本:

from flask import Flask, request, jsonify, make_response, json  
import base64  
import jwt  
from cryptography.hazmat.backends import default_backend  
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_pem_private_key  
import time  
import os  
import hashlib
import urllib2

app = Flask(__name__)

SERVICE = 'token-service'  
ISSUER = 'registry-token-issuer'


class DockerRegistryAuth(object):

	def __init__(self, account, scopes, service, issuer, private_key_path, token_expires=300):
		self.account = account
		self.issuer = issuer
		self.scopes = scopes
		self.access = self.get_access_by_scopes(scopes)
		self.service = service
		self.private_key_path = private_key_path
		self.token_expires = token_expires

	@property
	def private_key(self):
		if getattr(self, '_private_key_content', None):
			return self._private_key_content

		with open(self.private_key_path, 'r') as fp:
			setattr(self, '_private_key_content', fp.read())
			return self._private_key_content

	@property
	def public_key(self):
		private_key = load_pem_private_key(
				self.private_key,
				password=None,
				backend=default_backend()
				)
		_public_key = private_key.public_key()
		return _public_key

	def check_service(self, service):
		return self.service == service

	def get_token(self):
		now = int(time.time())

		claim = {
				'iss': self.issuer,
				'sub': self.account,
				'aud': self.service,
				'exp': now + self.token_expires,
				'nbf': now,
				'iat': now,
				'jti': base64.b64encode(os.urandom(1024)),
				'access': self.access
				}

		headers = {
				'kid': self.get_kid()
				}
		token = jwt.encode(claim, self.private_key, algorithm='RS256', headers=headers)
		return {
				'token': token,
				'issued_at': now,
				'expires_in': now + self.token_expires
				}

	def get_kid(self):
		der_public_key = self.public_key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)

		sha256 = hashlib.sha256(der_public_key)
		base32_payload = base64.b32encode(sha256.digest()[:30]) # 240bits / 8
		return ":".join(
			[base32_payload[i:i+4] for i in xrange(0, 48, 4)]
		)


	def get_login_info(self, authorization):
		if not authorization:
			return None
		auth_info = authorization
		if authorization.startswith('Basic'):
			auth_info = authorization[5:]

		user_info = base64.b64decode(auth_info)
		self.username, self.password = user_info.split(':')
		return {
				'username': self.username,
				'password': self.password,
				}

	def get_access_by_scopes(self, scopes):
		access = list()
		if not scopes:
			return access
		for scope in scopes:
			type_, name, actions = scope.split(':')
			access.append({
				'type': type_,
				'name': name,
				'actions': actions.split(',')
			})

		return access

def unauthorized401(access, message=None, code=None):  
	detail = list()
	for scope in access:
		for action in scope['actions']:
			detail.append({
				"Action": action,
				"Name": scope['name'],
				"Type": scope['type']
				})
	data = {
			"errors": [
				{
					"code": code or "UNAUTHORIZED",
					"detail": detail,
					"message": message or "access to the requested resource is not authorized"
					}
				]
	}
	resp = make_response(json.dumps(data), 401)
	resp.headers['Content-Type'] = 'application/json; charset=utf-8'
	resp.headers['Docker-Distribution-Api-Version'] = 'registry/2.0'
	resp.headers['Www-Authenticate'] = 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,push"'
	return resp


@app.route('/service/token')
def service_token():  
	print request.headers
	scopes = request.args.getlist('scope')
	account = request.args.get('account')
	client_id = request.args.get('client_id')
	service = request.args.get('service')
	http_base_auth = request.headers.get('Authorization')

	registry_auth = DockerRegistryAuth(account, scopes, SERVICE, ISSUER, './private_key.pem')
	if not registry_auth.check_service(service):
		return unauthorized401(registry_auth.access, 'service not be allowed.')
	user = registry_auth.get_login_info(http_base_auth)

	if user:
		#if not (user['username'] == 'atest' and user['password'] == 'atest'):
		if not (radisAuth(user['username'],user['password'])):
			return unauthorized401(registry_auth.access, 'incorrect username or password')
		res = registry_auth.get_token()
		return jsonify(
				token=res['token'],
			)

	return unauthorized401(registry_auth.access)

def radisAuth(user,pwd):
	url = "http://laravel.secsys.intra.sina.com.cn/vdlogin.php?u=%s&p=%s&t=%s" % (user,pwd,'a32286d166f9be3ef47e7a8efbb0da60')
	return urllib2.urlopen(url).read() == "OK"

if __name__ == '__main__':  
	app.run(host='0.0.0.0', port=8080, debug=True)

7.安装需要的包:
pip install PyJWT

8. 启动认证脚本:
python auth.py

9. 测试登录:
docker login localhost:5000
test:test:x@s.com

然后成功获得token 则登录成功.

python urllib2 请求包含cookie信息

cookie_file = “/tmp/netcraft_cookie.txt”
global_cookie = cookielib.MozillaCookieJar(cookie_file)
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(global_cookie))

pq = PyQuery(opener.open(sso_url).read())

csrf_token = pq(“input[name=’csrf_token’]”).attr(“value”)
destination = pq(“input[name=’destination’]”).attr(“value”)
print “get page \n csrf_token : %s \n destination: %s ” % (csrf_token,destination)
global_cookie.save(ignore_discard=True, ignore_expires=True)

python 带有选项的程序

保存如下内容为a.py

from optparse import OptionParser

parser = OptionParser()
parser.add_option(“-f”,”–file”,dest=”keyname”,help=”help to this option”,metavar=”PLACEHOLDER”,default=”default value”)
(options,args) = parser.parse_args()

print options,args

python a.py –help
就可以看到对应的参数