プロキシの除外ホスト (#6244)

* プロキシの除外ホスト

* オブジェクトストレージとの通信にProxyを使うかを選択できるように

* fix lint

* コメント

Co-authored-by: rinsuki <428rinsuki+git@gmail.com>
This commit is contained in:
MeiMei 2020-04-12 20:32:34 +09:00 committed by GitHub
parent 7ac8b672c5
commit 69dd52920e
12 changed files with 89 additions and 16 deletions

View file

@ -142,6 +142,11 @@ id: 'aid'
# Proxy for HTTP/HTTPS # Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128 #proxy: http://127.0.0.1:3128
#proxyBypassHosts: [
# 'example.com',
# '192.0.2.8'
#]
# Proxy for SMTP/SMTPS # Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT #proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4 #proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4

View file

@ -454,6 +454,8 @@ objectStorageRegion: "Region"
objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は、空または'us-east-1'にしてください。" objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は、空または'us-east-1'にしてください。"
objectStorageUseSSL: "SSLを使用する" objectStorageUseSSL: "SSLを使用する"
objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください" objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください"
objectStorageUseProxy: "Proxyを利用する"
objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
serverLogs: "サーバーログ" serverLogs: "サーバーログ"
deleteAll: "全て削除" deleteAll: "全て削除"
showFixedPostForm: "タイムライン上部に投稿フォームを表示する" showFixedPostForm: "タイムライン上部に投稿フォームを表示する"

View file

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from 'typeorm';
export class AddObjectStorageUseProxy1586624197029 implements MigrationInterface {
name = 'AddObjectStorageUseProxy1586624197029'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageUseProxy" boolean NOT NULL DEFAULT true`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageUseProxy"`, undefined);
}
}

View file

@ -116,6 +116,7 @@
<mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input> <mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input>
</div> </div>
<mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('objectStorageUseSSL') }}<template #desc>{{ $t('objectStorageUseSSLDesc') }}</template></mk-switch> <mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('objectStorageUseSSL') }}<template #desc>{{ $t('objectStorageUseSSLDesc') }}</template></mk-switch>
<mk-switch v-model="objectStorageUseProxy" :disabled="!useObjectStorage">{{ $t('objectStorageUseProxy') }}<template #desc>{{ $t('objectStorageUseProxyDesc') }}</template></mk-switch>
</template> </template>
</div> </div>
<div class="_footer"> <div class="_footer">
@ -249,6 +250,7 @@ export default Vue.extend({
objectStorageAccessKey: null, objectStorageAccessKey: null,
objectStorageSecretKey: null, objectStorageSecretKey: null,
objectStorageUseSSL: false, objectStorageUseSSL: false,
objectStorageUseProxy: false,
enableTwitterIntegration: false, enableTwitterIntegration: false,
twitterConsumerKey: null, twitterConsumerKey: null,
twitterConsumerSecret: null, twitterConsumerSecret: null,
@ -303,6 +305,7 @@ export default Vue.extend({
this.objectStorageAccessKey = this.meta.objectStorageAccessKey; this.objectStorageAccessKey = this.meta.objectStorageAccessKey;
this.objectStorageSecretKey = this.meta.objectStorageSecretKey; this.objectStorageSecretKey = this.meta.objectStorageSecretKey;
this.objectStorageUseSSL = this.meta.objectStorageUseSSL; this.objectStorageUseSSL = this.meta.objectStorageUseSSL;
this.objectStorageUseProxy = this.meta.objectStorageUseProxy;
this.enableTwitterIntegration = this.meta.enableTwitterIntegration; this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
this.twitterConsumerKey = this.meta.twitterConsumerKey; this.twitterConsumerKey = this.meta.twitterConsumerKey;
this.twitterConsumerSecret = this.meta.twitterConsumerSecret; this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
@ -411,6 +414,7 @@ export default Vue.extend({
objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null, objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null, objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
objectStorageUseSSL: this.objectStorageUseSSL, objectStorageUseSSL: this.objectStorageUseSSL,
objectStorageUseProxy: this.objectStorageUseProxy,
enableTwitterIntegration: this.enableTwitterIntegration, enableTwitterIntegration: this.enableTwitterIntegration,
twitterConsumerKey: this.twitterConsumerKey, twitterConsumerKey: this.twitterConsumerKey,
twitterConsumerSecret: this.twitterConsumerSecret, twitterConsumerSecret: this.twitterConsumerSecret,

View file

@ -35,6 +35,7 @@ export type Source = {
proxy?: string; proxy?: string;
proxySmtp?: string; proxySmtp?: string;
proxyBypassHosts?: string[];
accesslog?: string; accesslog?: string;

View file

@ -2,7 +2,7 @@ import * as fs from 'fs';
import * as stream from 'stream'; import * as stream from 'stream';
import * as util from 'util'; import * as util from 'util';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { httpAgent, httpsAgent } from './fetch'; import { getAgentByUrl } from './fetch';
import { AbortController } from 'abort-controller'; import { AbortController } from 'abort-controller';
import config from '../config'; import config from '../config';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
@ -25,7 +25,7 @@ export async function downloadUrl(url: string, path: string) {
}, },
timeout: 10 * 1000, timeout: 10 * 1000,
signal: controller.signal, signal: controller.signal,
agent: u => u.protocol == 'http:' ? httpAgent : httpsAgent, agent: getAgentByUrl,
}); });
if (!response.ok) { if (!response.ok) {

View file

@ -13,7 +13,7 @@ export async function getJson(url: string, accept = 'application/json, */*', tim
Accept: accept Accept: accept
}, headers || {}), }, headers || {}),
timeout, timeout,
agent: u => u.protocol == 'http:' ? httpAgent : httpsAgent, agent: getAgentByUrl,
}); });
if (!res.ok) { if (!res.ok) {
@ -27,17 +27,46 @@ export async function getJson(url: string, accept = 'application/json, */*', tim
return await res.json(); return await res.json();
} }
export const httpAgent = config.proxy /**
? new HttpProxyAgent(config.proxy) * Get http non-proxy agent
: new http.Agent({ */
const _http = new http.Agent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 30 * 1000, keepAliveMsecs: 30 * 1000,
}); });
export const httpsAgent = config.proxy /**
? new HttpsProxyAgent(config.proxy) * Get https non-proxy agent
: new https.Agent({ */
const _https = new https.Agent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 30 * 1000, keepAliveMsecs: 30 * 1000,
lookup: cache.lookup, lookup: cache.lookup,
}); });
/**
* Get http proxy or non-proxy agent
*/
export const httpAgent = config.proxy
? new HttpProxyAgent(config.proxy)
: _http;
/**
* Get https proxy or non-proxy agent
*/
export const httpsAgent = config.proxy
? new HttpsProxyAgent(config.proxy)
: _https;
/**
* Get agent by URL
* @param url URL
* @param bypassProxy Allways bypass proxy
*/
export function getAgentByUrl(url: URL, bypassProxy = false) {
if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
return url.protocol == 'http:' ? _http : _https;
} else {
return url.protocol == 'http:' ? httpAgent : httpsAgent;
}
}

View file

@ -348,4 +348,9 @@ export class Meta {
default: true, default: true,
}) })
public objectStorageUseSSL: boolean; public objectStorageUseSSL: boolean;
@Column('boolean', {
default: true,
})
public objectStorageUseProxy: boolean;
} }

View file

@ -6,7 +6,7 @@ import config from '../../config';
import { ILocalUser } from '../../models/entities/user'; import { ILocalUser } from '../../models/entities/user';
import { UserKeypairs } from '../../models'; import { UserKeypairs } from '../../models';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { httpsAgent } from '../../misc/fetch'; import { getAgentByUrl } from '../../misc/fetch';
export default async (user: ILocalUser, url: string, object: any) => { export default async (user: ILocalUser, url: string, object: any) => {
const timeout = 10 * 1000; const timeout = 10 * 1000;
@ -25,7 +25,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const req = https.request({ const req = https.request({
agent: httpsAgent, agent: getAgentByUrl(new URL(`https://example.net`)),
protocol, protocol,
hostname, hostname,
port, port,

View file

@ -394,6 +394,10 @@ export const meta = {
objectStorageUseSSL: { objectStorageUseSSL: {
validator: $.optional.bool validator: $.optional.bool
}, },
objectStorageUseProxy: {
validator: $.optional.bool
}
} }
}; };
@ -632,6 +636,10 @@ export default define(meta, async (ps, me) => {
set.objectStorageUseSSL = ps.objectStorageUseSSL; set.objectStorageUseSSL = ps.objectStorageUseSSL;
} }
if (ps.objectStorageUseProxy !== undefined) {
set.objectStorageUseProxy = ps.objectStorageUseProxy;
}
await getConnection().transaction(async transactionalEntityManager => { await getConnection().transaction(async transactionalEntityManager => {
const meta = await transactionalEntityManager.findOne(Meta, { const meta = await transactionalEntityManager.findOne(Meta, {
order: { order: {

View file

@ -190,6 +190,7 @@ export default define(meta, async (ps, me) => {
response.objectStorageAccessKey = instance.objectStorageAccessKey; response.objectStorageAccessKey = instance.objectStorageAccessKey;
response.objectStorageSecretKey = instance.objectStorageSecretKey; response.objectStorageSecretKey = instance.objectStorageSecretKey;
response.objectStorageUseSSL = instance.objectStorageUseSSL; response.objectStorageUseSSL = instance.objectStorageUseSSL;
response.objectStorageUseProxy = instance.objectStorageUseProxy;
} }
return response; return response;

View file

@ -1,8 +1,12 @@
import * as S3 from 'aws-sdk/clients/s3'; import * as S3 from 'aws-sdk/clients/s3';
import { Meta } from '../../models/entities/meta'; import { Meta } from '../../models/entities/meta';
import { httpsAgent, httpAgent } from '../../misc/fetch'; import { getAgentByUrl } from '../../misc/fetch';
export function getS3(meta: Meta) { export function getS3(meta: Meta) {
const u = meta.objectStorageEndpoint != null
? `${meta.objectStorageUseSSL ? 'https://' : 'http://'}${meta.objectStorageEndpoint}`
: `${meta.objectStorageUseSSL ? 'https://' : 'http://'}example.net`;
return new S3({ return new S3({
endpoint: meta.objectStorageEndpoint || undefined, endpoint: meta.objectStorageEndpoint || undefined,
accessKeyId: meta.objectStorageAccessKey!, accessKeyId: meta.objectStorageAccessKey!,
@ -11,7 +15,7 @@ export function getS3(meta: Meta) {
sslEnabled: meta.objectStorageUseSSL, sslEnabled: meta.objectStorageUseSSL,
s3ForcePathStyle: !!meta.objectStorageEndpoint, s3ForcePathStyle: !!meta.objectStorageEndpoint,
httpOptions: { httpOptions: {
agent: meta.objectStorageUseSSL ? httpsAgent : httpAgent agent: getAgentByUrl(new URL(u), !meta.objectStorageUseProxy)
} }
}); });
} }