Skip to content

Commit

Permalink
Add authorization support for checking token at server (PoC) (#146)
Browse files Browse the repository at this point in the history
* wip for add token authorization to grpc

* workable auth poc

* 0.25.2

* finish auth poc

* 0.25.3

* 0.25.4
  • Loading branch information
huan authored Aug 9, 2021
1 parent 25a1e91 commit e647002
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 76 deletions.
13 changes: 5 additions & 8 deletions examples/auth/.gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
ca.crt
ca.key
client.crt
client.csr
client.key
server.crt
server.csr
server.key
# all generated openssl files
*.crt
*.key
*.csr
*.srl
6 changes: 5 additions & 1 deletion examples/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ Use Wechaty TOKEN for authorization.

## DEVELOPMENT

1. Enable debug: `export GRPC_TRACE=all`
Enable debug trace messages:

```sh
export GRPC_TRACE=all
```

## GLOSSARY

Expand Down
40 changes: 20 additions & 20 deletions examples/auth/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CallMetadataGenerator } from '@grpc/grpc-js/build/src/call-credentials'
import fs from 'fs'

import {
Expand All @@ -23,31 +24,30 @@ export async function testDing (client: PuppetClient) {

async function main () {
const TOKEN = '__token__'
void TOKEN

const rootCerts = fs.readFileSync('root-ca.crt')

/**
* With server authentication SSL/TLS and a custom header with token
* https://grpc.io/docs/guides/auth/#with-server-authentication-ssltls-and-a-custom-header-with-token-1
*/
const metaCallback: CallMetadataGenerator = (_params, callback) => {
const meta = new grpc.Metadata()
// metadata.add('authorization', `Wechaty ${TOKEN}`)
callback(null, meta)
}

const headerCreds = grpc.credentials.createFromMetadataGenerator((_callMetaOptoins, callback) => {
const metadata = new grpc.Metadata()
metadata.add('authorization', `Wechaty ${TOKEN}`)
callback(null, metadata)
})

// const certChain = fs.readFileSync('client.crt')
// const privateKey = fs.readFileSync('client.key')
// const rootCerts = null // fs.readFileSync('ca.crt')
void fs

const creds = grpc.credentials.combineChannelCredentials(
// grpc.credentials.createInsecure(),
grpc.credentials.createSsl(), // rootCerts, privateKey, certChain),
headerCreds,
)

// const creds = grpc.credentials.createInsecure()
const channelCred = grpc.credentials.createSsl(rootCerts)
const callCred = grpc.credentials.createFromMetadataGenerator(metaCallback)
const combCreds = grpc.credentials.combineChannelCredentials(channelCred, callCred)

const client = new PuppetClient(
'localhost:8788',
creds,
combCreds,
{
'grpc.default_authority': 'puppet_token',
'grpc.default_authority': '__token__',
'grpc.ssl_target_name_override': 'wechaty-puppet-service',
},
)

Expand Down
28 changes: 15 additions & 13 deletions examples/auth/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,31 @@
set -e

PASSPHRASE=wechaty
DAYS=3650
SNI=wechaty-puppet-service

# CA
openssl genrsa -passout pass:"$PASSPHRASE" -des3 -out ca.key 4096
openssl req -passin pass:"$PASSPHRASE" -key ca.key -out ca.crt \
-x509 -new -days 3650 \
-subj "/C=US/ST=San Francisco/L=Palo Alto/O=Wechaty/OU=CA/CN=ca"
openssl genrsa -passout pass:"$PASSPHRASE" -des3 -out root-ca.key 4096
openssl req -passin pass:"$PASSPHRASE" -key root-ca.key -out root-ca.crt \
-x509 -new -days "$DAYS" \
-subj "/C=US/ST=San Francisco/L=Palo Alto/O=Wechaty/OU=CA/CN=wechaty-root-ca"

# Server
openssl genrsa -passout pass:"$PASSPHRASE" -des3 -out server.key 4096
openssl genrsa -passout pass:"$PASSPHRASE" -des3 -out server.key 1024
openssl req -passin pass:"$PASSPHRASE" -new -out server.csr -key server.key \
-subj "/C=US/ST=San Francisco/L=Palo Alto/O=Wechaty/OU=Server/CN=localhost"
-subj "/C=US/ST=San Francisco/L=Palo Alto/O=Wechaty/OU=Puppet/CN=${SNI}"

openssl x509 -req -passin pass:"$PASSPHRASE" -days 3650 -set_serial 01 \
-CA ca.crt -CAkey ca.key \
openssl x509 -req -passin pass:"$PASSPHRASE" -days "$DAYS" -set_serial 01 \
-CA root-ca.crt -CAkey root-ca.key \
-in server.csr -out server.crt
openssl rsa -passin pass:"$PASSPHRASE" -in server.key -out server.key

# Client
openssl genrsa -passout pass:"$PASSPHRASE" -des3 -out client.key 4096
openssl req -passin pass:"$PASSPHRASE" -new -key client.key -out client.csr \
-subj "/C=US/ST=San Francisco/L=Palo Alto/O=Wechaty/OU=Client/CN=localhost"
openssl x509 -passin pass:"$PASSPHRASE" -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
openssl rsa -passin pass:"$PASSPHRASE" -in client.key -out client.key
# openssl genrsa -passout pass:"$PASSPHRASE" -des3 -out client.key 1024
# openssl req -passin pass:"$PASSPHRASE" -new -key client.key -out client.csr \
# -subj "/C=US/ST=San Francisco/L=Palo Alto/O=Wechaty/OU=Client/CN=${SNI}"
# openssl x509 -passin pass:"$PASSPHRASE" -req -days "$DAYS" -in client.csr -CA root-ca.crt -CAkey root-ca.key -set_serial 01 -out client.crt
# openssl rsa -passin pass:"$PASSPHRASE" -in client.key -out client.key



Expand Down
3 changes: 3 additions & 0 deletions examples/auth/raw-https/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# See

- [Monkey patching tls in node.js to support self-signed certificates with custom root certificate authorities](https://medium.com/trabe/monkey-patching-tls-in-node-js-to-support-self-signed-certificates-with-custom-root-cas-25c7396dfd2a)
17 changes: 17 additions & 0 deletions examples/auth/raw-https/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import fs from 'fs'
import https from 'https'

console.info('faint')

https.request({
ca: [fs.readFileSync('./rootCA.crt')],
hostname: '127.0.0.1',
method: 'GET',
path: '/',
port: 6000,
// rejectUnauthorized: false,
}, res => {
res.on('data', data => {
process.stdout.write(data)
})
}).end()
17 changes: 17 additions & 0 deletions examples/auth/raw-https/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# Private key for the root cert
openssl genrsa -des3 -out rootCA.key 4096

# root certificate
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 365 -out rootCA.crt

# Private key for the server cert
openssl genrsa -out server.key 2048

# Signing request for the server
openssl req -new -key server.key -out server.csr

# Server cert using the root certificate
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
-out server.crt -days 365 -sha256
15 changes: 15 additions & 0 deletions examples/auth/raw-https/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import fs from 'fs'
import https from 'https'

const server = https.createServer({
cert: fs.readFileSync('./server.crt'),
key: fs.readFileSync('./server.key'),
// ca: fs.readFileSync('./rootCA.crt')
})

server.on('request', (_req, res) => {
res.writeHead(200)
res.end('Alive!\n')
})

server.listen(6000)
157 changes: 132 additions & 25 deletions examples/auth/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,145 @@ import {
import {
puppetServerImpl,
} from '../../tests/puppet-server-impl'
import { StatusBuilder } from '@grpc/grpc-js'
import {
StatusBuilder,
Metadata,
UntypedHandleCall,
} from '@grpc/grpc-js'
// import { Http2SecureServer } from 'http2'
import {
sendUnaryData,
ServerUnaryCall,
} from '@grpc/grpc-js/build/src/server-call'

const puppetServerExample: IPuppetServer = {
...puppetServerImpl,
import http2 from 'http2'

ding: (call, callback) => {
const data = call.request.getData()
console.info(`ding(${data})`)
console.info('metadata:', call.metadata.getMap())
console.info('getPeer:', call.getPeer())
console.info('getDeadLine:', call.getDeadline())
function monkeyPatchMetadataFromHttp2Headers (
MetadataClass: typeof Metadata,
): void {
const fromHttp2Headers = MetadataClass.fromHttp2Headers
MetadataClass.fromHttp2Headers = function (
headers: http2.IncomingHttpHeaders
): Metadata {
const metadata = fromHttp2Headers.call(MetadataClass, headers)

if (metadata.get('authorization').length <= 0) {
const authority = headers[':authority']
const authorization = `Wechaty ${authority}`
metadata.set('authorization', authorization)
}
return metadata
}
}

/**
* The following handlers using `cb` for errors
* handleUnaryCall
* handleClientStreamingCall
*/
type ServiceHandlerCb = (call: ServerUnaryCall<any, any>, cb: sendUnaryData<any>) => void
/**
* The following handlers using `emit` for errors
* handleServerStreamingCall
* handleBidiStreamingCall
*/
type ServiceHandlerEmit = (call: ServerUnaryCall<any, any>) => void
type ServiceHandler = ServiceHandlerCb | ServiceHandlerEmit

/**
* Huan(202108): wrap the following handle calls with authorization:
* - handleUnaryCall
* - handleClientStreamingCall
* - handleServerStreamingCall
* - handleBidiStreamingCall
*
* See:
* https://grpc.io/docs/guides/auth/#with-server-authentication-ssltls-and-a-custom-header-with-token
*/
function authHandler (
validToken : string,
handler : UntypedHandleCall,
): ServiceHandler {
console.info('wrapAuthHandler', handler.name)
return function (
call: ServerUnaryCall<any, any>,
cb?: sendUnaryData<any>,
) {
// console.info('wrapAuthHandler internal')

const authorization = call.metadata.get('authorization')[0]
// console.info('authorization', authorization)

let errMsg = ''
if (typeof authorization === 'string') {
if (authorization.startsWith('Wechaty ')) {
const token = authorization.substring(8 /* 'Wechaty '.length */)
if (token === validToken) {

return handler(
call as any,
cb as any,
)

} else {
errMsg = `Invalid Wechaty TOKEN "${token}"`
}
} else {
const type = authorization.split(/\s+/)[0]
errMsg = `Invalid authorization type: "${type}"`
}
} else {
errMsg = 'No Authorization found.'
}

/**
* Not authorized
*/
const error = new StatusBuilder()
.withCode(GrpcServerStatus.UNAUTHENTICATED)
.withDetails('The server need "Authorization: Wechaty TOKEN" to accept the request')
.withDetails(errMsg)
.withMetadata(call.metadata)
.build()

void error
callback(null, new DingResponse())
if (cb) {
cb(error)
} else if ('emit' in call) {
call.emit('error', error)
} else {
throw new Error('no callback and call is not emit-able')
}
}
}

const wechatyAuthToken = (validToken: string) => (
puppetServer: IPuppetServer,
) => {
for (const [key, val] of Object.entries(puppetServer)) {
puppetServer[key] = authHandler(validToken, val)
}
return puppetServer
}

monkeyPatchMetadataFromHttp2Headers(Metadata)

const puppetServerExample: IPuppetServer = {
...puppetServerImpl,

// console.info('getDeadLine:', call.)
// callback(error, new DingResponse())
ding: (call, callback) => {
const data = call.request.getData()
console.info(`ding(${data})`)
console.info('authorization:', call.metadata.getMap()['authorization'])
callback(null, new DingResponse())
},
}

async function main () {
const puppetServerExampleWithAuth = wechatyAuthToken('__token__')(puppetServerExample)

const server = new grpc.Server()
server.addService(
PuppetService,
puppetServerExample,
puppetServerExampleWithAuth,
)

const serverBindPromise = util.promisify(
Expand All @@ -52,19 +160,18 @@ async function main () {

void fs

const rootCerts: null | Buffer = null // fs.readFileSync('ca.crt')
const keyCertPairs: grpc.KeyCertPair[] = []
// [{
// cert_chain : fs.readFileSync('server.crt'),
// private_key : fs.readFileSync('server.key'),
// }]
const checkClientCertificate = false
const rootCerts: null | Buffer = fs.readFileSync('root-ca.crt')
void rootCerts
const keyCertPairs: grpc.KeyCertPair[] = [{
cert_chain : fs.readFileSync('server.crt'),
private_key : fs.readFileSync('server.key'),
}]
// const checkClientCertificate = false

const port = await serverBindPromise(
'127.0.0.1:8788',
'0.0.0.0:8788',
// grpc.ServerCredentials.createInsecure(),
grpc.ServerCredentials.createSsl(rootCerts, keyCertPairs, checkClientCertificate),

grpc.ServerCredentials.createSsl(null, keyCertPairs) //, checkClientCertificate),
)
console.info('Listen on port:', port)
server.start()
Expand Down
11 changes: 3 additions & 8 deletions openapi/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ module github.com/wechaty/grpc/openapi
go 1.16

require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/protobuf v1.4.3 // indirect
github.com/google/protobuf v3.15.0+incompatible // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.1.0
github.com/wechaty/go-grpc v0.18.12 // indirect
google.golang.org/grpc v1.34.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
google.golang.org/protobuf v1.25.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
Loading

0 comments on commit e647002

Please sign in to comment.