Android模拟器认证流程
1 分析create_web_container.sh脚本
通过执行create_web_container.sh来创建Android模拟器和Web环境的dockerCompose
DOCKER_YAML=js/docker/docker-compose-build.yaml
PASSWDS="$USER,hello"
while getopts 'hasip:' flag; do
case "${flag}" in
a) DOCKER_YAML="${DOCKER_YAML} -f js/docker/development.yaml" ;;
p) PASSWDS="${OPTARG}" ;;
h) help ;;
s) START='yes' ;;
i) INSTALL='yes' ;;
*) help ;;
esac
done
# Now generate the public/private keys and salt the password
cd js/jwt-provider
pip install -r requirements.txt >/dev/null
python gen-passwords.py --pairs "${PASSWDS}" || exit 1
cp jwt_secrets_pub.jwks ../docker/certs/jwt_secrets_pub.jwks
# compose the container
pip install docker-compose >/dev/null
docker-compose -f ${DOCKER_YAML} build
- 如果执行create_web_container.sh脚本时不指定-p选项,默认创建一套账户密码体系,其中账户名是$USER,默认的密码是hello
- 如果执行脚本时指定了-p选项,${PASSWDS}会将账户密码传递给gen-passwords.py脚本
- 通过pip安装requirements.txt文件中指定了库,例如JWCrypto用来创建公私钥文件
- 执行gen-passwords.py脚本,根据传入的${PASSWDS}创建passwd文件保存账户信息,并创建公钥和私钥文件
- 将gen-passwords.py脚本生成的公钥文件jwt_secrets_pub.jwks拷贝到../docker/certs/目录
- 执行docker-compose -f js/docker/docker-compose-build.yaml build
2 分析gen-passwords.py脚本
gen-passwords.py脚本的作用是生成passwd、jwt_secrets_pub.jwks和jwt_secrets_priv.jwks三个文件。
FLAGS = flags.FLAGS
flags.DEFINE_list("pairs", [getpass.getuser() + "@localhost", "hello"], "List of user name password pairs. user1,passwd1,user2,passwd2,...")
flags.DEFINE_string("passwords", "passwd", "File with json dictionary of users -> password hash")
flags.DEFINE_string("jwks", "jwt_secrets", "File name with generated JWKS secrets.")
flags.register_validator(
"pairs", lambda value: len(value) % 2 == 0, message="--pairs must have an even number of user,password pairs"
)
def pairwise(iterable):
"s -> (s0, s1), (s2, s3), (s4, s5), ..."
a = iter(iterable)
return zip(a, a)
def main(argv):
if len(argv) > 1:
raise app.UsageError("Too many command-line arguments.")
# Create salted passwords
unsalted = pairwise(FLAGS.pairs)
salted = {}
for pair in unsalted:
logging.info("%s : %s", pair[0], pair[1])
salted[pair[0]] = generate_password_hash(pair[1])
# And write them to a file
with open(FLAGS.passwords, "w") as f:
f.write(json.dumps(salted))
# Create the jwks secrets and export them
keys = jwk.JWK.generate(kty="RSA", size=2048)
# Python 2 does signed crc32, unlike Python 3
kid = hex(binascii.crc32(keys.export_public().encode('utf-8')) & 0xFFFFFFFF)
public = json.loads(keys.export_public())
private = json.loads(keys.export_private())
public["kid"] = kid
private["kid"] = kid
public_jwks = {"keys": [public]}
private_jwks = {"keys": [private]}
with open(FLAGS.jwks + '_pub.jwks', 'w') as f:
f.write(json.dumps(public_jwks, indent=2))
with open(FLAGS.jwks + '_priv.jwks', 'w') as f:
f.write(json.dumps(private_jwks, indent=2))
if __name__ == "__main__":
app.run(main)
- 调用generate_password_hash函数对传入的用户密码执行hash加密,passwd文件最后保存了用户名和加密后的用户密码
- 调用JWCrypto库来创建jwt_secrets_pub.jwks公钥文件和jwt_secrets_priv.jwks私钥文件
passwd文件
{"wxudong": "pbkdf2:sha256:150000$QAhcdEXl$194fa653eb2234ae929a40b460d723f5845de2b85cdf7fc57fe87d8c1f9d8468"}
jwt_secrets_pub.jwks公钥文件
{
"keys": [
{
"e": "AQAB",
"kty": "RSA",
"n": "s6CohlzVmDPjn9kat4lusYkvN0hpnAnJ5Hf6IYNqNq1jQUQD6B47k7HT_3XWr2MvRZYC59J5mK73IYLGpQIio9WEikoQog2sV8m7GRuSeo44qcLELpgJku8NJ3dQG0OflwVVWgRaS525i62jgc8MYl_eujKWtqOMesEI3UsDYEp83kNXIoHpO1t_g6XPwFaZTg3k0hG5PfSM5aaB79YfFKYi3GCdxjM4MjJR_-YPjZFDCVoikcYVnNu52w4gPUldBJv2svRsnQM6Xps5VugqkkxDszq5zxP-TRdwalP17EAdPWfs2GrRuC8fEevRgNZODgLwQpeJbRoYkQz3U8rYvw",
"kid": "0x10034a08"
}
]
}
jwt_secrets_priv.jwk私钥文件
{
"keys": [
{
"d": "Dvv48vRtkPvLIjt_Ig5h4Id8G9V7kduzLs7fW8pVougF3pzo4oUbHS_5alcPKKRSfjCMX4BMSnNWBEKfhYZPE3GtU8fn6UzQsqYOaILHTlfs3CR2LxjZu5sbcs5eLVgPyQ5V12ODkMlAgClk-WAnPVGYB9pOfj_YaSkPLz6hsneAXq4NGAUwQ1OaDDT28VC-RsQpC9geOt-xaZkfpO_66DCrjeMJrGm_v5tNdLyO1eX4tBTic9W7nDwHUEvAjst9U1s6bomwfVvdOxv7oVOR18plWIQKpjbwZ44Eu09PPHhAQkI3XTNwHVy6lSDZY4m3XayQrvKJk5wsDGNSTkE2AQ",
"dp": "LnqJIuSX-d_vFRdupWqF3e92jGzIf6BKQf60dZTUsf8F0jpIdh2wVPIfFIxMzIJsvDH6lMeOfvOoqPhVK_dLHto5mbtpgQ3G9XE1m8e_HzDaqeciV0JZDn-rjC1vjX6X-NKt3aXWVu9EdIfgxYaA1zpa8aeB6psYpW-cN8jqZgE",
"dq": "m95utBuxolIEzpmKUorC3ot-cvPhgTW5L9RnbBi8dbETnSpITBP0bFBvTZsK2Snt-wC1UJeRC4DR4ENIOXwsj9a_x_jY8OE8DC2a82O1PTTpOl6wVFlGpIwPMX_ZMZOPJDJEdXeYauJEHX7FScXvQZUARNUoO1LrIahF50uQIZ8",
"e": "AQAB",
"kty": "RSA",
"n": "s6CohlzVmDPjn9kat4lusYkvN0hpnAnJ5Hf6IYNqNq1jQUQD6B47k7HT_3XWr2MvRZYC59J5mK73IYLGpQIio9WEikoQog2sV8m7GRuSeo44qcLELpgJku8NJ3dQG0OflwVVWgRaS525i62jgc8MYl_eujKWtqOMesEI3UsDYEp83kNXIoHpO1t_g6XPwFaZTg3k0hG5PfSM5aaB79YfFKYi3GCdxjM4MjJR_-YPjZFDCVoikcYVnNu52w4gPUldBJv2svRsnQM6Xps5VugqkkxDszq5zxP-TRdwalP17EAdPWfs2GrRuC8fEevRgNZODgLwQpeJbRoYkQz3U8rYvw",
"p": "57X-KInt0h5RzyGbOJzxe0s3a3E8zffblwRe9XhuHq9WNurnbpztUVSh_pX5FUn8KAM_a7sMoPjJhm0YCUdLZ5Y6a4OWdk7AYk5X4lk-4QlYMPPkZOL9kBRyvEo6ZmUisMtHZYeTMk_oZOE31rOrrI82VN_wJpPUO9Pxp9h43wE",
"q": "xnT_v8Ep6-pUx_IULsvAKrlYwQW_JCsVD3r8SYxy61zbPgD99StETVoSpAwJv8Gl71USrpBJ8OtzkvyvY38Y-M9VJQVPrcTzEi0lGiYcJP5WBxd-RFVrZDlfbxZ_nd30gpIiU12imbQwcMXrKfAAHfEPFXAVK2Y7b5tf1PTzd78",
"qi": "A0tQFhLjDGCoiYXIYtBdAPEmk_EW9nnz2VF8d1-dNvVtyfSAIHg3Clp1MdH6kmZ6ZzFUgNcex0kHe2CD_oopow2t7XOiZzlfA29SOJbkSocgwopFV13X5Dj9iVUH-wShaNelg44VleV7k5rUus74gbJafAujSsImm9IpfE2k2jA",
"kid": "0x10034a08"
}
]
}
3 执行docker-compose-build.yaml文件
version: "3.7"
services:
front-envoy:
image: emulator_envoy:latest
build:
context: .
dockerfile: envoy.Dockerfile
networks:
- envoymesh
expose:
- "8080"
- "8001"
- "8443"
ports:
- "80:8080"
- "443:8443"
- "8001:8001"
emulator:
image: emulator_emulator:latest
build:
context: ../../src
dockerfile: Dockerfile
networks:
envoymesh:
aliases:
- emulator
devices: [/dev/kvm]
shm_size: 128M
expose:
- "8556"
jwt_signer:
image: emulator_jwt_signer:latest
build:
context: ../jwt-provider
dockerfile: Dockerfile
networks:
envoymesh:
aliases:
- jwt_signer
expose:
- "8080"
nginx:
image: emulator_nginx:latest
build:
context: ..
dockerfile: docker/nginx.Dockerfile
networks:
envoymesh:
aliases:
- nginx
expose:
- "80"
networks:
envoymesh: {}
Docker Compose 配置文件语法:
image 指定使用的镜像
build 指定Dockerfile构建,context指定了路径,dockerfile指定了文件名
networks 网络配置,容器部署在名为envoymesh的网络中
ports 端口映射
expose 暴露端口
devices 设置映射列表
shm_size 设置容器 /dev/shm 分区大小
3.1 创建emulator_nginx:latest镜像
nginx.Dockerfile指定了两个FROM,第一个FROM指定了编译镜像,将web程序的代码拷贝到app目录,建立npm环境来编译web程序。第二个FORM指定了最终要生成nginx镜像,将Build后的文件拷贝到该镜像中,Dockerfile执行完成后会删除编译镜像保留nginx镜像。nginx暴露了80端口
# Stage 0, "build-stage", based on Node.js, to build and compile the frontend
FROM tiangolo/node-frontend:10 as build-stage
WORKDIR /app
COPY package*.json /app/
RUN npm install
COPY ./ /app/
ARG configuration=production
RUN npm run build
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
FROM nginx:1.16
COPY --from=build-stage /app/build/ /usr/share/nginx/html
# Copy the default nginx.conf provided by tiangolo/node-frontend
COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf
3.2 创建emulator_jwt_signer:latest镜像
创建该镜像时会将jwt-provider目录中的文件(包括公钥文件、私钥文件、passwd文件)拷贝到app目录,使用pip安装了认证程序所需要的库文件,最后执行jwt-provider.py脚本。该镜像被用作JWT认证服务器(JWTS),暴露了8080端口。
当Dockerfile中指定ENTRYPOINT后, CMD的含义不再是直接运行其命令,而是将CMD的内容作为参数 传给ENTRYPOINT ,实际执行变为<ENTRYPOINT> "<CMD>"
FROM debian:stretch-slim
COPY sources.list /etc/apt/
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
COPY pip.conf /etc/pip.conf
RUN pip install -r requirements.txt
ENTRYPOINT ["python"]
CMD ["jwt-provider.py"]
3.3 创建emulator_emulator:latest镜像
将项目src目录的文件拷贝到docker镜像中指定的目录,包括指定模拟器版本编译后的可执行文件,指定Android rom编译后的image文件,启动模拟器脚本launch-emulator.sh等。emulator暴露了8556端口用于gRPC。启动后执行脚本CMD ["/android/sdk/launch-emulator.sh"]
FROM debian:stretch-slim AS emulator
RUN sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
# Install all the required emulator dependencies.
# You can get these by running ./android/scripts/unix/run_tests.sh --verbose --verbose --debs | grep apt | sort -u
# pulse audio is needed due to some webrtc dependencies.
RUN apt-get update && apt-get install -y --no-install-recommends \
# Emulator & video bridge dependencies
libc6 libdbus-1-3 libfontconfig1 libgcc1 \
libpulse0 libtinfo5 libx11-6 libxcb1 libxdamage1 \
libnss3 libxcomposite1 libxcursor1 libxi6 \
libxext6 libxfixes3 zlib1g libgl1 pulseaudio socat \
# Enable turncfg through usage of curl
curl ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Now we configure the user account under which we will be running the emulator
RUN mkdir -p /android/sdk/platforms && \
mkdir -p /android/sdk/platform-tools && \
mkdir -p /android/sdk/system-images/android && \
mkdir -p /android-home
# Make sure to place files that do not change often in the higher layers
# as this will improve caching.
COPY launch-emulator.sh /android/sdk/
COPY platform-tools/adb /android/sdk/platform-tools/adb
COPY default.pa /etc/pulse/default.pa
RUN gpasswd -a root audio && \
chmod +x /android/sdk/launch-emulator.sh /android/sdk/platform-tools/adb
COPY avd/ /android-home
COPY emu/ /android/sdk/
COPY sys/ /android/sdk/system-images/android/
# Create an initial snapshot so we will boot fast next time around,
# This is currently an experimental feature, and is not easily configurable//
# RUN --security=insecure cd /android/sdk && ./launch-emulator.sh -quit-after-boot 120
# This is the console port, you usually want to keep this closed.
EXPOSE 5554
# This is the ADB port, useful.
EXPOSE 5555
# This is the gRPC port, also useful, we don't want ADB to incorrectly identify this.
EXPOSE 8556
ENV ANDROID_SDK_ROOT /android/sdk
ENV ANDROID_AVD_HOME /android-home
WORKDIR /android/sdk
# You will need to make use of the grpc snapshot/webrtc functionality to actually interact with
# the emulator.
CMD ["/android/sdk/launch-emulator.sh"]
# Note we should use gRPC status endpoint to check for health once the canary release is out.
HEALTHCHECK --interval=30s \
--timeout=30s \
--start-period=30s \
--retries=3 \
CMD /android/sdk/platform-tools/adb shell getprop dev.bootcomplete | grep "1"
# Date frequently changes, so we place this in the last layer.
LABEL maintainer="wxudong@pc" \
SystemImage.Abi=x86_64 \
SystemImage.TagId=android \
SystemImage.GpuSupport=true \
AndroidVersion.ApiLevel=28 \
com.google.android.emulator.build-date="2020-03-10T03:36:04.968500Z" \
com.google.android.emulator.description="Pixel 2 Emulator, running API 28" \
com.google.android.emulator.version="android-28-x86_64/30.0.0"
3.4 创建emulator_envoy:latest镜像
拷贝配置文件envoy.yaml,拷贝证书文件、key、公钥文件到指定目录,容器启动后执行envoy.yaml配置文件。
FROM envoyproxy/envoy:v1.12.0
COPY ./envoy.yaml /etc/envoy/envoy.yaml
ADD certs/self_sign.crt /etc/cert.crt
ADD certs/self_sign.key /etc/key.key
ADD certs/jwt_secrets_pub.jwks /etc/jwt_secrets_pub.jwks
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
4 认证流程
认证流程图.png1:web client获取用户输入的账户和密码,发送一个带有"/token"的GET网络请求
2:envoy过滤"/token",转发到jwt_signer容器
3:jwt验证账户和密码,并将私钥加密的token信息(包含iss、aud属性)返回给web client
4:web client进行gRPC远程调用时,携带加密的token信息
5:envoy使用http filter过滤器,过滤"/android.emulation.control.EmulatorController"(gPRC请求),公钥解密token信息后,验证iss、aud属性,如果匹配网络请求再转发到emulator。
4.1 web client发送获取token的网络请求
web应用程序启动时调用TokenAuthService,设置auth_uri为http://localhost:443/token
文件App.js
var EMULATOR_GRPC =
window.location.protocol +
"//" +
window.location.hostname +
":" +
window.location.port;
if (development) {
EMULATOR_GRPC = "http://localhost:8080";
}
export default class App extends Component {
constructor(props) {
super(props);
if (development) {
this.auth = new NoAuthService();
} else {
this.auth = new TokenAuthService(EMULATOR_GRPC + "/token");
}
this.emulator = new EmulatorControllerService(EMULATOR_GRPC, this.auth, this.onError);
}
}
当用户输入用户账号密码后,调用login获取token信息。
doLogin = () => {
const { auth } = this.props;
const { email, password } = this.state;
auth.login(email, password).catch(e => {
this.setState({ displayErrorSnack: true });
});
};
// Login when we press the enter key
handleTextFieldKeyDown = event => {
if (event.key === "Enter") this.doLogin();
};
传入账号和密码,根据设置的auth_uri发送axios.get网络请求,response.data返回token信息。
login = (email, password) => {
return axios
.get(this.auth_uri, {
auth: {
username: email,
password: password
}
})
.then(response => {
this.token = "Bearer " + response.data;
this.events.emit("authorized", true);
});
};
4.2 envoy转发获取token的请求
envoy会过滤根据/token过滤网络请求,转发到jwt_signer容器上去。
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
# This is the emulator endpoint for grpc requests.
- match: { prefix: "/android.emulation.control.EmulatorController" }
route:
cluster: emulator_service_grpc
max_grpc_timeout: 0s
# This is the JWT token provider, responsible for handing our secure tokens.
- match: { prefix: "/token" }
route: { cluster: jwt_signer }
# The rest will be available under our webapp.
- match: { prefix: "/" }
route: { cluster: nginx }
cors:
allow_origin: ["*"]
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
- name: jwt_signer
connect_timeout: 0.250s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: jwt_signer
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: jwt_signer
port_value: 8080
4.3 jwt_signer生成token信息并返回
verify_password根据传入的用户名和密码,验证有效性,passwd文件中保存了用户名和经过hash算法加密的密码,该文件在创建jwt镜像时已经被拷贝。
get_token会设置token信息,并将token信息返回
- kid envoy用来获取解密的密钥
- iss、aud 这两个属性需要跟envoy.yaml配置文件中的issuer和audiences匹配,用于网络过滤
- exp 有效登录的截止时间
- iat 有效登录的起始时间
- name 用户名
flags.DEFINE_string('passwd', 'passwd', 'The json password file used to verify access, generated by running gen-passwords.py')
flags.DEFINE_string('jwk', 'jwt_secrets_priv.jwks', 'The jwk webkey used for signing, generated by running gen-passwords.py')
@auth.verify_password
def verify_password(username, password):
logging.info("user: %s - exists: %s",username, username in users)
if username in users:
return check_password_hash(users.get(username), password)
return False
@api.route('/token', methods=['GET'])
@auth.login_required
def get_token():
token = {
# The KeyID, envoy will use this to pick the proper decryption key.
'kid' : private_key[0],
# Both the 'iss' and 'aud' must match what is expected
# in the envoy.yaml configuration
# under "issuer" and "audiences", without it the token will be rejected.
'iss' : 'android-emulator@jwt-provider.py',
'aud' : 'android.emulation.control.EmulatorController',
# we give users 2 hours of access.
'exp' : datetime.now() + timedelta(hours=2),
'iat' : datetime.now(),
'name' : auth.username()
}
return jwt.encode(token, private_key[1], algorithm='RS256')
4.4 web client gRPC远程调用时携带token信息
当web应用程序每次进行gRPC调用时会添加token信息,例如调用sendKey
emulator_controller_grpc_web_pb.js
proto.android.emulation.control.EmulatorControllerPromiseClient.prototype.sendKey =
function(request, metadata) {
return this.client_.rpcCall(this.hostname_ +
'/android.emulation.control.EmulatorController/sendKey',
request,
metadata || {},
methodInfo_EmulatorController_sendKey);
};
emulator_web_client.js
class EmulatorWebClient extends GrpcWebClientBase {
...
rpcCall = (method, request, metadata, methodinfo, callback) => {
const authHeader = this.auth.authHeader();
const meta = { ...metadata, ...authHeader };
const self = this;
return super.rpcCall(method, request, meta, methodinfo, (err, res) => {
if (err) {
if (err.code === 401) self.auth.unauthorized();
if (self.events)
self.events.emit("error", err);
}
if (callback) callback(err, res);
});
};
...
}
4.5 envoy验证token信息,并转发到emulator
envoy支持JWT Authentication,HTTP filter过滤器可以被用来验证JSON Web Token (JWT),验证signature、audiences和issuer,还可以用来检测有效的登录时间,如果JWT验证失败,这次网络请求会被拒绝访问。filter的名字需要设置成"envoy.filters.http.jwt_authn"。
- providers 指定了JWT应该如何验证,JWKS公钥的位置等
- rules 指定了match匹配规则
envoy会过滤"/android.emulation.control.EmulatorController"的网络请求,转发到 emulator-jwt provider处,通过本地公钥文件/etc/jwt_secrets_pub.jwks解密token信息,验证issuer和audiences属性是否匹配,如果验证成功则网路请求会被放过。
envoy.yaml
static_resources:
listeners:
- name: tls_redirect
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stream_idle_timeout: 0s # Needed for streaming support.
codec_type: auto
stat_prefix: ingress_http
route_config:
virtual_hosts:
- name: backend
domains:
- ["*"]
routes:
- match:
prefix: "/"
redirect:
path_redirect: "/"
https_redirect: true
http_filters:
- name: envoy.router
config: {}
- name: tls_secure
address:
socket_address: { address: 0.0.0.0, port_value: 8443 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
# This is the emulator endpoint for grpc requests.
- match: { prefix: "/android.emulation.control.EmulatorController" }
route:
cluster: emulator_service_grpc
max_grpc_timeout: 0s
# This is the JWT token provider, responsible for handing our secure tokens.
- match: { prefix: "/token" }
route: { cluster: jwt_signer }
# The rest will be available under our webapp.
- match: { prefix: "/" }
route: { cluster: nginx }
cors:
allow_origin: ["*"]
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
http_filters:
# We setup a JWT authentication endpoint in front of the gRPC engine.
# This will enforce JWT token validation before we forward the gRPC call
- name: envoy.filters.http.jwt_authn
config:
providers:
emulator-jwt:
issuer: android-emulator@jwt-provider.py
audiences:
- android.emulation.control.EmulatorController
local_jwks:
# The secrets that are used by the token service to properly
# sign the jwt tokens.
filename: /etc/jwt_secrets_pub.jwks
rules:
- match: { prefix: "/android.emulation.control.EmulatorController" }
requires: { provider_name: "emulator-jwt" }
- name: envoy.grpc_web
- name: envoy.cors
- name: envoy.router
tls_context:
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "/etc/cert.crt"
private_key:
filename: "/etc/key.key"
clusters:
- name: emulator_service_grpc
connect_timeout: 0.250s
type: strict_dns
lb_policy: round_robin
http2_protocol_options: {}
load_assignment:
cluster_name: emulator_service_grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: emulator
port_value: 8556
- name: jwt_signer
connect_timeout: 0.250s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: jwt_signer
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: jwt_signer
port_value: 8080
- name: nginx
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: nginx
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: nginx
port_value: 80
admin:
access_log_path: "/dev/stdout"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
5 多账户多模拟器认证
5.1 Header消息头中添加标识
根据不同的用户名,返回不同的Header标识
authAndroidVersion = () => {
if(this.user == "USER1"){
return { AndroidVersion: "android9" };
}
if(this.user == "USER2"){
return { AndroidVersion: "android10" };
}
}
grpc请求时添加Header标识
rpcCall = (method, request, metadata, methodinfo, callback) => {
const authHeader = this.auth.authHeader();
const authAndroidVersion = this.auth.authAndroidVersion();
const meta = { ...metadata, ...authHeader, ...authAndroidVersion};
const self = this;
return super.rpcCall(method, request, meta, methodinfo, (err, res) => {
if (err) {
if (err.code === 401) self.auth.unauthorized();
if (self.events)
self.events.emit("error", err);
}
if (callback) callback(err, res);
});
};
5.2 envoy过滤转发
根据不同额Header标识,转发到不同的cluster,对应到不同的模拟器版本
- match:
prefix: "/android.emulation.control.EmulatorController"
headers:
- name: AndroidVersion
exact_match: android9
route:
cluster: emulator_service_grpc_9
max_grpc_timeout: 0s
- match:
prefix: "/android.emulation.control.EmulatorController"
headers:
- name: AndroidVersion
exact_match: android10
route:
cluster: emulator_service_grpc_10
max_grpc_timeout: 0s
5.3 docker-compose-build启动多个模拟器
emulator_9:
image: emulator_emulator_9:latest
build:
context: ../../src_9
dockerfile: Dockerfile
networks:
envoymesh:
aliases:
- emulator_9
devices: [/dev/kvm]
shm_size: 128M
expose:
- "8556"
emulator_10:
image: emulator_emulator_10:latest
build:
context: ../../src_10
dockerfile: Dockerfile
networks:
envoymesh:
aliases:
- emulator_10
devices: [/dev/kvm]
shm_size: 128M
expose:
- "8557"