From 11e526470d7b0186fe93b27ce0418adc077b4826 Mon Sep 17 00:00:00 2001 From: zhangshuai1171 Date: Tue, 19 Apr 2022 17:35:43 +0800 Subject: [PATCH] feat: init --- .conf-struct.yml | 70 - .env.example | 45 +- .env.prod | 18 - .gitignore | 1 + .gitlab-ci.yml | 12 - Dockerfile | 20 +- Makefile | 22 +- README.md | 188 +- api/helper/api_helper.go | 2 +- api/router.go | 28 +- api/v1/ca/ca.go | 6 +- api/v1/ca/config.go | 14 +- api/v1/ca/intermediate.go | 18 +- api/v1/ca/overall.go | 52 +- api/v1/ca/workload_topo.go | 20 +- api/v1/certleaf/base.go | 6 +- api/v1/certleaf/cert_chain.go | 26 +- api/v1/health/health.go | 81 +- api/v1/vault/vault.go | 50 - api/v1/workload/lifecycle.go | 52 +- api/v1/workload/workload.go | 107 +- api/v1/workload/workload_test.go | 23 - bin/capitalizone | Bin 67731456 -> 0 bytes ca/certmanager/cleaner.go | 142 - ca/datastore/datastore.go | 16 +- ca/datastore/migration.go | 22 +- ca/keymanager/csr_templates.go | 8 +- ca/keymanager/keeper.go | 86 +- ca/keymanager/remote_signer.go | 32 +- ca/keymanager/self_signer.go | 22 +- ca/keymanager/upper_client.go | 22 +- ca/ocsp/metrics.go | 6 +- ca/ocsp/source.go | 42 +- ca/revoke/metrics.go | 6 +- ca/revoke/revoke.go | 58 +- ca/signer/handler.go | 42 +- ca/signer/metrics.go | 6 +- ca/singleca/endpoint.go | 42 +- ca/singleca/file_watcher.go | 10 +- ca/singleca/root.go | 94 +- ca/upperca/checker.go | 20 +- ca/upperca/client.go | 4 +- cmd/{http.go => api.go} | 14 +- cmd/ocsp.go | 22 +- cmd/tls.go | 19 +- conf.default.yml | 86 - conf.prod.yml | 13 +- conf.test.yml | 23 +- config.json | 60 +- core/config/types.go | 46 +- core/state.go | 29 +- .../mysql/cfssl-model/dao/certificates.go | 2 +- database/mysql/cfssl-model/dao/forbid.go | 2 +- .../mysql/cfssl-model/dao/ocsp_responses.go | 2 +- .../mysql/cfssl-model/dao/self_keypair.go | 2 +- database/mysql/migration.go | 6 +- docker-compose.yml | 11 - docs/docs.go | 143 +- docs/swagger.json | 143 +- docs/swagger.yaml | 145 +- examples/cfssl-model/dao/certificates.go | 111 - examples/cfssl-model/dao/dao_base.go | 90 - examples/cfssl-model/dao/forbid.go | 111 - examples/cfssl-model/dao/ocsp_responses.go | 111 - examples/cfssl-model/dao/schema_migrations.go | 111 - examples/cfssl-model/dao/self_keypair.go | 111 - examples/cfssl-model/model/certificates.go | 379 --- examples/cfssl-model/model/forbid.go | 192 -- examples/cfssl-model/model/model_base.go | 102 - examples/cfssl-model/model/ocsp_responses.go | 163 -- .../cfssl-model/model/schema_migrations.go | 115 - examples/cfssl-model/model/self_keypair.go | 219 -- go.mod | 36 +- go.sum | 252 +- initer/client.go | 62 +- initer/election.go | 48 - initer/flags.go | 55 +- initer/initer.go | 55 +- initer/logger.go | 9 +- initer/metrics.go | 310 --- initer/registry.go | 3 - logic/ca/base.go | 6 +- logic/ca/config.go | 14 +- logic/ca/intermediate.go | 32 +- logic/ca/workload.go | 28 +- logic/certleaf/base.go | 6 +- logic/certleaf/certleaf.go | 17 +- logic/events/logger.go | 24 +- logic/schema/cert.go | 12 +- logic/schema/getter.go | 12 +- logic/schema/influx.go | 6 +- logic/workload/base.go | 6 +- logic/workload/display.go | 22 +- logic/workload/lifecycle.go | 76 +- logic/workload/unit.go | 8 +- main.go | 24 +- mtls-server.Dockerfile | 4 - pkg/attrmgr/attrmgr_test.go | 107 - pkg/caclient/cai.go | 25 +- pkg/caclient/cert_manager.go | 38 +- pkg/caclient/examples/certmanager/server.go | 103 - pkg/caclient/examples/certrotator/server.go | 93 - pkg/caclient/examples/client/client.go | 23 +- pkg/caclient/examples/fakeclient/client.go | 102 - pkg/caclient/examples/gatekeeper/main.go | 159 -- pkg/caclient/examples/logger/logger.go | 10 - pkg/caclient/examples/sentry-build/main.go | 88 - pkg/caclient/examples/server/server.go | 26 +- pkg/caclient/examples/util/cert_parser.go | 20 +- pkg/caclient/exchanger.go | 33 +- pkg/caclient/http.go | 2 +- pkg/caclient/logger.go | 4 +- pkg/caclient/ocsp_impl.go | 14 +- pkg/caclient/ocsp_interface.go | 2 +- pkg/caclient/ocsp_mem_cache.go | 26 +- pkg/caclient/revoke.go | 12 +- pkg/caclient/rorate_controller.go | 43 +- pkg/caclient/test/bench_test.go | 14 +- pkg/caclient/test/cert_test.go | 12 +- pkg/caclient/test/main_test.go | 4 +- pkg/caclient/test/mtls_test.go | 28 +- pkg/caclient/tls.go | 30 +- pkg/caclient/transport.go | 52 +- pkg/caclient/transport_test.go | 4 +- pkg/certinfo/certinfo.go | 585 ---- pkg/certinfo/certinfo_test.go | 85 - pkg/certinfo/test_certs/README | 21 - pkg/certinfo/test_certs/leaf1.cert.pem | 53 - pkg/certinfo/test_certs/leaf1.cert.text | 35 - pkg/certinfo/test_certs/leaf1.cfg | 27 - pkg/certinfo/test_certs/leaf1.csr.pem | 31 - pkg/certinfo/test_certs/leaf1.csr.text | 19 - pkg/certinfo/test_certs/leaf1.key.pem | 9 - pkg/certinfo/test_certs/leaf2.cert.pem | 69 - pkg/certinfo/test_certs/leaf2.cert.text | 48 - pkg/certinfo/test_certs/leaf2.cfg | 27 - pkg/certinfo/test_certs/leaf2.csr.pem | 49 - pkg/certinfo/test_certs/leaf2.csr.text | 31 - pkg/certinfo/test_certs/leaf2.key.pem | 14 - pkg/certinfo/test_certs/leaf3.cert.pem | 54 - pkg/certinfo/test_certs/leaf3.cert.text | 37 - pkg/certinfo/test_certs/leaf3.cfg | 27 - pkg/certinfo/test_certs/leaf3.csr.pem | 32 - pkg/certinfo/test_certs/leaf3.csr.text | 21 - pkg/certinfo/test_certs/leaf3.key.pem | 8 - pkg/certinfo/test_certs/make-certs.sh | 57 - pkg/certinfo/test_certs/new-keys.sh | 14 - pkg/certinfo/test_certs/root1.cert.pem | 53 - pkg/certinfo/test_certs/root1.cert.text | 35 - pkg/certinfo/test_certs/root1.cfg | 69 - pkg/certinfo/test_certs/root1.csr.pem | 32 - pkg/certinfo/test_certs/root1.csr.text | 19 - pkg/certinfo/test_certs/root1.key.pem | 9 - pkg/discover/k8s/k8s.go | 208 -- pkg/event/report.go | 62 - pkg/influxdb/client.go | 8 +- pkg/influxdb/config.go | 20 +- pkg/influxdb/influxdb-client/README.md | 38 + pkg/influxdb/influxdb-client/influxdb.go | 870 ++++++ .../influxdb-client/models/inline_fnv.go | 32 + .../models/inline_strconv_parse.go | 44 + pkg/influxdb/influxdb-client/models/points.go | 2413 +++++++++++++++++ pkg/influxdb/influxdb-client/models/rows.go | 62 + .../influxdb-client/models/statistic.go | 42 + pkg/influxdb/influxdb-client/models/time.go | 74 + .../influxdb-client/models/uint_support.go | 8 + .../influxdb-client/pkg/escape/bytes.go | 115 + .../influxdb-client/pkg/escape/strings.go | 21 + pkg/influxdb/influxdb-client/v2/client.go | 811 ++++++ pkg/influxdb/influxdb-client/v2/params.go | 73 + pkg/influxdb/influxdb-client/v2/udp.go | 116 + pkg/influxdb/metrics.go | 25 +- pkg/keygen/genkey.go | 20 +- pkg/keygen/genkey_test.go | 4 +- pkg/keyprovider/xkey_provider.go | 17 +- pkg/keyprovider/xkey_provider_test.go | 4 +- pkg/kutil/clock/clock.go | 393 --- pkg/kutil/errors/doc.go | 18 - pkg/kutil/errors/errors.go | 249 -- pkg/kutil/framer/framer.go | 167 -- pkg/kutil/intstr/generated.pb.go | 372 --- pkg/kutil/intstr/generated.proto | 43 - pkg/kutil/intstr/intstr.go | 185 -- pkg/kutil/json/json.go | 156 -- pkg/kutil/mergepatch/OWNERS | 7 - pkg/kutil/mergepatch/errors.go | 102 - pkg/kutil/mergepatch/util.go | 133 - pkg/kutil/naming/from_stack.go | 93 - pkg/kutil/net/http.go | 724 ----- pkg/kutil/net/interface.go | 457 ---- pkg/kutil/net/port_range.go | 149 - pkg/kutil/net/port_split.go | 77 - pkg/kutil/net/util.go | 56 - pkg/kutil/runtime/runtime.go | 172 -- pkg/kutil/sets/byte.go | 205 -- pkg/kutil/sets/doc.go | 20 - pkg/kutil/sets/empty.go | 23 - pkg/kutil/sets/int.go | 205 -- pkg/kutil/sets/int32.go | 205 -- pkg/kutil/sets/int64.go | 205 -- pkg/kutil/sets/string.go | 205 -- pkg/kutil/strategicpatch/OWNERS | 8 - pkg/kutil/strategicpatch/errors.go | 49 - pkg/kutil/strategicpatch/meta.go | 194 -- pkg/kutil/strategicpatch/patch.go | 2172 --------------- pkg/kutil/strategicpatch/types.go | 193 -- pkg/kutil/validation/field/errors.go | 272 -- pkg/kutil/validation/field/path.go | 91 - pkg/kutil/validation/validation.go | 503 ---- pkg/kutil/wait/doc.go | 19 - pkg/kutil/wait/wait.go | 606 ----- pkg/kutil/yaml/decoder.go | 348 --- pkg/logger/example/single_test.go | 46 + pkg/logger/log.go | 168 ++ pkg/logger/logger.go | 139 + pkg/logger/logger_test.go | 33 + pkg/logger/redis_hook/loggers.go | 29 + pkg/logger/redis_hook/redis_hook.go | 74 + pkg/logger/zapcore_impl.go | 166 ++ pkg/memorycacher/README.md | 83 + pkg/memorycacher/cache.go | 1221 +++++++++ pkg/memorycacher/cache_test.go | 1783 ++++++++++++ pkg/memorycacher/sharded.go | 208 ++ pkg/memorycacher/sharded_test.go | 92 + pkg/mesh/service.go | 28 - pkg/resource/file_source.go | 121 - pkg/resource/file_source/cpu_from_file.go | 262 -- pkg/resource/file_source/disk.go | 90 - pkg/resource/file_source/memory_from_file.go | 143 - pkg/resource/file_source/net.go | 80 - pkg/resource/file_source/netstat.go | 22 - pkg/resource/http_source.go | 52 - pkg/resource/http_source/cpu_from_url.go | 269 -- pkg/resource/http_source/disk_from_url.go | 90 - pkg/resource/http_source/memory_from_url.go | 115 - pkg/resource/http_source/net.go | 72 - pkg/resource/http_source/netstat.go | 5 - pkg/resource/resource.go | 48 - pkg/signature/signer.go | 8 +- pkg/signature/signer_test.go | 6 +- pkg/signature/util.go | 8 +- pkg/vaultinit/init.go | 204 -- pkg/vaultsecret/secret.go | 4 +- start.sh | 5 - telegraf.conf | 12 - test/benchmark/client/client.go | 8 +- test/benchmark/server/server.go | 6 +- test/fake/fake_server.go | 26 - test/fake/modules/fake_clients.go | 126 - test/fake/tools/unique_id_generator.go | 40 - test/fakeca/ca-key.pem | 51 - test/fakeca/ca.pem | 31 - test/fakeca/root-certs.pem | 29 - test/tcp/main.go | 40 - util/bodyBuffer/buf.go | 47 - util/bodyBuffer/buf_test.go | 79 - util/cache.go | 2 +- util/flow.go | 21 - util/json.go | 14 - util/redis.go | 1 - util/slice.go | 9 - util/strings.go | 1 - util/types/etcd.go | 5 - util/types/event.go | 8 - util/types/sql.go | 49 - util/util.go | 304 --- 266 files changed, 10128 insertions(+), 18513 deletions(-) delete mode 100644 .conf-struct.yml delete mode 100644 .env.prod delete mode 100644 .gitlab-ci.yml delete mode 100644 api/v1/vault/vault.go delete mode 100644 api/v1/workload/workload_test.go delete mode 100755 bin/capitalizone delete mode 100644 ca/certmanager/cleaner.go rename cmd/{http.go => api.go} (81%) delete mode 100644 conf.default.yml delete mode 100644 docker-compose.yml delete mode 100644 examples/cfssl-model/dao/certificates.go delete mode 100644 examples/cfssl-model/dao/dao_base.go delete mode 100644 examples/cfssl-model/dao/forbid.go delete mode 100644 examples/cfssl-model/dao/ocsp_responses.go delete mode 100644 examples/cfssl-model/dao/schema_migrations.go delete mode 100644 examples/cfssl-model/dao/self_keypair.go delete mode 100644 examples/cfssl-model/model/certificates.go delete mode 100644 examples/cfssl-model/model/forbid.go delete mode 100644 examples/cfssl-model/model/model_base.go delete mode 100644 examples/cfssl-model/model/ocsp_responses.go delete mode 100644 examples/cfssl-model/model/schema_migrations.go delete mode 100644 examples/cfssl-model/model/self_keypair.go delete mode 100644 initer/election.go delete mode 100644 initer/metrics.go delete mode 100644 initer/registry.go delete mode 100644 mtls-server.Dockerfile delete mode 100644 pkg/attrmgr/attrmgr_test.go delete mode 100644 pkg/caclient/examples/certmanager/server.go delete mode 100644 pkg/caclient/examples/certrotator/server.go delete mode 100644 pkg/caclient/examples/fakeclient/client.go delete mode 100644 pkg/caclient/examples/gatekeeper/main.go delete mode 100644 pkg/caclient/examples/logger/logger.go delete mode 100644 pkg/caclient/examples/sentry-build/main.go delete mode 100644 pkg/certinfo/certinfo.go delete mode 100644 pkg/certinfo/certinfo_test.go delete mode 100644 pkg/certinfo/test_certs/README delete mode 100644 pkg/certinfo/test_certs/leaf1.cert.pem delete mode 100644 pkg/certinfo/test_certs/leaf1.cert.text delete mode 100644 pkg/certinfo/test_certs/leaf1.cfg delete mode 100644 pkg/certinfo/test_certs/leaf1.csr.pem delete mode 100644 pkg/certinfo/test_certs/leaf1.csr.text delete mode 100644 pkg/certinfo/test_certs/leaf1.key.pem delete mode 100644 pkg/certinfo/test_certs/leaf2.cert.pem delete mode 100644 pkg/certinfo/test_certs/leaf2.cert.text delete mode 100644 pkg/certinfo/test_certs/leaf2.cfg delete mode 100644 pkg/certinfo/test_certs/leaf2.csr.pem delete mode 100644 pkg/certinfo/test_certs/leaf2.csr.text delete mode 100644 pkg/certinfo/test_certs/leaf2.key.pem delete mode 100644 pkg/certinfo/test_certs/leaf3.cert.pem delete mode 100644 pkg/certinfo/test_certs/leaf3.cert.text delete mode 100644 pkg/certinfo/test_certs/leaf3.cfg delete mode 100644 pkg/certinfo/test_certs/leaf3.csr.pem delete mode 100644 pkg/certinfo/test_certs/leaf3.csr.text delete mode 100644 pkg/certinfo/test_certs/leaf3.key.pem delete mode 100644 pkg/certinfo/test_certs/make-certs.sh delete mode 100644 pkg/certinfo/test_certs/new-keys.sh delete mode 100644 pkg/certinfo/test_certs/root1.cert.pem delete mode 100644 pkg/certinfo/test_certs/root1.cert.text delete mode 100644 pkg/certinfo/test_certs/root1.cfg delete mode 100644 pkg/certinfo/test_certs/root1.csr.pem delete mode 100644 pkg/certinfo/test_certs/root1.csr.text delete mode 100644 pkg/certinfo/test_certs/root1.key.pem delete mode 100644 pkg/discover/k8s/k8s.go delete mode 100644 pkg/event/report.go create mode 100644 pkg/influxdb/influxdb-client/README.md create mode 100644 pkg/influxdb/influxdb-client/influxdb.go create mode 100644 pkg/influxdb/influxdb-client/models/inline_fnv.go create mode 100644 pkg/influxdb/influxdb-client/models/inline_strconv_parse.go create mode 100644 pkg/influxdb/influxdb-client/models/points.go create mode 100644 pkg/influxdb/influxdb-client/models/rows.go create mode 100644 pkg/influxdb/influxdb-client/models/statistic.go create mode 100644 pkg/influxdb/influxdb-client/models/time.go create mode 100644 pkg/influxdb/influxdb-client/models/uint_support.go create mode 100644 pkg/influxdb/influxdb-client/pkg/escape/bytes.go create mode 100644 pkg/influxdb/influxdb-client/pkg/escape/strings.go create mode 100644 pkg/influxdb/influxdb-client/v2/client.go create mode 100644 pkg/influxdb/influxdb-client/v2/params.go create mode 100644 pkg/influxdb/influxdb-client/v2/udp.go delete mode 100644 pkg/kutil/clock/clock.go delete mode 100644 pkg/kutil/errors/doc.go delete mode 100644 pkg/kutil/errors/errors.go delete mode 100644 pkg/kutil/framer/framer.go delete mode 100644 pkg/kutil/intstr/generated.pb.go delete mode 100644 pkg/kutil/intstr/generated.proto delete mode 100644 pkg/kutil/intstr/intstr.go delete mode 100644 pkg/kutil/json/json.go delete mode 100644 pkg/kutil/mergepatch/OWNERS delete mode 100644 pkg/kutil/mergepatch/errors.go delete mode 100644 pkg/kutil/mergepatch/util.go delete mode 100644 pkg/kutil/naming/from_stack.go delete mode 100644 pkg/kutil/net/http.go delete mode 100644 pkg/kutil/net/interface.go delete mode 100644 pkg/kutil/net/port_range.go delete mode 100644 pkg/kutil/net/port_split.go delete mode 100644 pkg/kutil/net/util.go delete mode 100644 pkg/kutil/runtime/runtime.go delete mode 100644 pkg/kutil/sets/byte.go delete mode 100644 pkg/kutil/sets/doc.go delete mode 100644 pkg/kutil/sets/empty.go delete mode 100644 pkg/kutil/sets/int.go delete mode 100644 pkg/kutil/sets/int32.go delete mode 100644 pkg/kutil/sets/int64.go delete mode 100644 pkg/kutil/sets/string.go delete mode 100644 pkg/kutil/strategicpatch/OWNERS delete mode 100644 pkg/kutil/strategicpatch/errors.go delete mode 100644 pkg/kutil/strategicpatch/meta.go delete mode 100644 pkg/kutil/strategicpatch/patch.go delete mode 100644 pkg/kutil/strategicpatch/types.go delete mode 100644 pkg/kutil/validation/field/errors.go delete mode 100644 pkg/kutil/validation/field/path.go delete mode 100644 pkg/kutil/validation/validation.go delete mode 100644 pkg/kutil/wait/doc.go delete mode 100644 pkg/kutil/wait/wait.go delete mode 100644 pkg/kutil/yaml/decoder.go create mode 100644 pkg/logger/example/single_test.go create mode 100644 pkg/logger/log.go create mode 100644 pkg/logger/logger.go create mode 100644 pkg/logger/logger_test.go create mode 100644 pkg/logger/redis_hook/loggers.go create mode 100644 pkg/logger/redis_hook/redis_hook.go create mode 100644 pkg/logger/zapcore_impl.go create mode 100644 pkg/memorycacher/README.md create mode 100644 pkg/memorycacher/cache.go create mode 100644 pkg/memorycacher/cache_test.go create mode 100644 pkg/memorycacher/sharded.go create mode 100644 pkg/memorycacher/sharded_test.go delete mode 100644 pkg/mesh/service.go delete mode 100644 pkg/resource/file_source.go delete mode 100644 pkg/resource/file_source/cpu_from_file.go delete mode 100644 pkg/resource/file_source/disk.go delete mode 100644 pkg/resource/file_source/memory_from_file.go delete mode 100644 pkg/resource/file_source/net.go delete mode 100644 pkg/resource/file_source/netstat.go delete mode 100644 pkg/resource/http_source.go delete mode 100644 pkg/resource/http_source/cpu_from_url.go delete mode 100644 pkg/resource/http_source/disk_from_url.go delete mode 100644 pkg/resource/http_source/memory_from_url.go delete mode 100644 pkg/resource/http_source/net.go delete mode 100644 pkg/resource/http_source/netstat.go delete mode 100644 pkg/resource/resource.go delete mode 100644 pkg/vaultinit/init.go delete mode 100644 start.sh delete mode 100644 telegraf.conf delete mode 100644 test/fake/fake_server.go delete mode 100644 test/fake/modules/fake_clients.go delete mode 100644 test/fake/tools/unique_id_generator.go delete mode 100644 test/fakeca/ca-key.pem delete mode 100644 test/fakeca/ca.pem delete mode 100644 test/fakeca/root-certs.pem delete mode 100644 test/tcp/main.go delete mode 100644 util/bodyBuffer/buf.go delete mode 100644 util/bodyBuffer/buf_test.go delete mode 100644 util/flow.go delete mode 100644 util/json.go delete mode 100644 util/redis.go delete mode 100644 util/slice.go delete mode 100644 util/strings.go delete mode 100644 util/types/etcd.go delete mode 100644 util/types/event.go delete mode 100644 util/types/sql.go delete mode 100644 util/util.go diff --git a/.conf-struct.yml b/.conf-struct.yml deleted file mode 100644 index 4619eec..0000000 --- a/.conf-struct.yml +++ /dev/null @@ -1,70 +0,0 @@ -registry: - self-name: capitalizone - -log: - log-proxy: - host: "" - port: 6379 - key: ca_log - -redis: - nodes: [] - -# 级联 CA 配置项 -keymanager: - upper-ca: - - "" # https://user:password@server.ca - self-sign: false # 开启表示 ROOT CA - csr-templates: - root-ca: - o: CI123 ROOT AUTHORITY - expiry: 175200h - intermediate-ca: - o: SITE CA IDENTIFY - ou: "spiffe://site/cluster" - expiry: 175200h - -singleca: - config-path: "" - -election: - enabled: false - id: capitalizone-leader - baseon: configmap - always-leader: false - -gateway-nervs: - enabled: false - endpoint: "" - -http: - ca-listen: 0.0.0.0:8081 - listen: 0.0.0.0:8080 - -mysql: - dsn: "" - -influxdb: - enabled: true - address: "MSP_CUSTOM_INFLUXDB_ADDRESS" #192.168.2.80:8086 - port: 8086 - udp_address: "MSP_CUSTOM_INFLUXDB_UDP_ADDRESS" #influxdb msp数据库的udp地址,ip:port - database: "MSP_CUSTOM_INFLUXDB_DATABASES" # 数据库名称 - precision: "ms" #精度 n, u, ms, s, m or h - username: "MSP_CUSTOM_INFLUXDB_USERNAME" - password: "MSP_CUSTOM_INFLUXDB_PASSWORD" - read-username: "MSP_CUSTOM_INFLUXDB_USERNAME" - read-password: "MSP_CUSTOM_INFLUXDB_PASSWORD" - max-idle-conns: 30 - max-idle-conns-per-host: 30 - flush-size: 20 #批量发送的点的个数 - flush-time: 10 #定时批量发送点的时间,单位:s - -mesh: - msp-portal-api: "http://msp-portal:9080" - -swagger-enabled: false - -debug: false - -version: "0.1" \ No newline at end of file diff --git a/.env.example b/.env.example index 99194d9..de91b6b 100644 --- a/.env.example +++ b/.env.example @@ -1,26 +1,21 @@ -# Log Proxy -IS_LOG_LOG_PROXY_HOST=192.168.2.80 -IS_LOG_LOG_PROXY_PORT=6381 -# Redis -IS_REDIS_NODES="192.168.2.80:9001 192.168.2.80:9002 192.168.2.80:9003" -IS_GATEWAY_NERVS_ENABLED=false -IS_GATEWAY_NERVS_ENDPOINT="" -# Mysql -IS_MYSQL_DSN=root:123456@tcp(192.168.2.80:3306)/cap?charset=utf8mb4&parseTime=True&loc=Local -# Influxdb -IS_INFLUXDB_ENABLED=false -IS_INFLUXDB_ADDRESS= -IS_INFLUXDB_PORT= -IS_INFLUXDB_DATABASE= -IS_INFLUXDB_USERNAME= -IS_INFLUXDB_PASSWORD= -# Keymanager -IS_KEYMANAGER_UPPER_CA= +IS_INFLUXDB_ENABLED: true +IS_INFLUXDB_ADDRESS: 127.0.0.1 +IS_INFLUXDB_DATABASE: victoria +IS_INFLUXDB_PASSWORD: victoria +IS_INFLUXDB_PORT: "8427" +IS_INFLUXDB_READ_PASSWORD: victoria +IS_INFLUXDB_READ_USERNAME: victoria +IS_INFLUXDB_USERNAME: victoria IS_KEYMANAGER_SELF_SIGN=false -IS_CSR_TEMPLATES_INTERMEDIATE_CA_O= -IS_CSR_TEMPLATES_INTERMEDIATE_CA_OU= -IS_CSR_TEMPLATES_INTERMEDIATE_CA_EXPIRY=175200h -IS_CSR_TEMPLATES_ROOT_CA_O="CI123 ROOT AUTHORITY" -IS_CSR_TEMPLATES_ROOT_CA_EXPIRY=175200h -# HTTP -IS_HTTP_CA_LISTEN=0.0.0.0:8081 \ No newline at end of file +IS_KEYMANAGER_CSR_TEMPLATES_INTERMEDIATE_CA_O: site s105 huawei-shanghai-105 +IS_KEYMANAGER_CSR_TEMPLATES_INTERMEDIATE_CA_OU: spiffe://spiffeid/cluster +IS_KEYMANAGER_UPPER_CA: https://rootca-tls:8081 +IS_LOG_LOG_PROXY_HOST: redis-host +IS_LOG_LOG_PROXY_PORT: 6379 +IS_MYSQL_DSN: root:root@tcp(127.0.0.1:3306)/cap?charset=utf8mb4&parseTime=True&loc=Local +IS_OCSP_CACHE_TIME: 60 +IS_SINGLECA_CONFIG_PATH: /etc/capitalizone/config.json +IS_VAULT_ADDR: http://127.0.0.1:8200 +IS_VAULT_ENABLED: "false" +IS_VAULT_INIT: "true" +IS_VAULT_PREFIX: ca/ \ No newline at end of file diff --git a/.env.prod b/.env.prod deleted file mode 100644 index 473d1b3..0000000 --- a/.env.prod +++ /dev/null @@ -1,18 +0,0 @@ -IS_ENV: 'test' -IS_SINGLECA_CONFIG_PATH: './config.json' -IS_INFLUXDB_ENABLED: 'false' -IS_INFLUXDB_ADDRESS: '192.168.2.97' -IS_INFLUXDB_PORT: 9428 -IS_INFLUXDB_USERNAME: "influx-msp" -IS_INFLUXDB_PASSWORD: "sunyangpassword" -IS_INFLUXDB_READ_USERNAME: "influx-msp_select" -IS_INFLUXDB_READ_PASSWORD: "sunyangpassword" -IS_KEYMANAGER_UPPER_CA: "https://192.168.2.80:8380 https://192.168.2.97:8380" -IS_CSR_TEMPLATES_INTERMEDIATE_CA_O: 'TEST SERVER 80' -IS_CSR_TEMPLATES_INTERMEDIATE_CA_OU: 'spiffe://local-test/80' -IS_MYSQL_DSN: 'msp:msp123123@tcp(192.168.9.2:9002)/cap?charset=utf8mb4&parseTime=True&loc=Local' -IS_VAULT_ENABLED: 'true' -IS_VAULT_ADDR: 'https://vault.gw002.oneitfarm.com' -IS_VAULT_TOKEN: 's.8RRbqjcSRq2OYZVuwONeVQVu' -IS_VAULT_PREFIX: 'ca/' -IS_MIGRATION: 'true' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 71658fa..a5570d6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ # IDE configs .idea .vscode +./bin diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 74ba8a1..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,12 +0,0 @@ -stages: - - build - -build: - stage: build - image: harbor.oneitfarm.com/zhirenyun/docker:20.10-buildx - tags: - - aliyun-sh - script: - - docker buildx build -t $HARBOR_HOST/bifrost/capitalizone:$CI_COMMIT_REF_NAME --platform=linux/arm64,linux/amd64 . --push - only: - - tags \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c8badae..11977d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hub.oneitfarm.com/library/golang:1.17.8-alpine AS builder +FROM golang:1.17.8-alpine AS builder ENV GO111MODULE=on \ GOPROXY=https://goproxy.oneitfarm.com,https://goproxy.cn,direct @@ -6,17 +6,23 @@ ENV GO111MODULE=on \ WORKDIR /build COPY . . -RUN CGO_ENABLED=0 go build -o capitalizone . +RUN CGO_ENABLED=0 go build -o zaca . -FROM harbor.oneitfarm.com/bifrost/ubuntu:20.04 +FROM ubuntu:20.04 -WORKDIR /capitalizone +WORKDIR /zaca -COPY --from=builder /build/capitalizone . +COPY --from=builder /build/zaca . COPY --from=builder /build/database/mysql/migrations ./database/mysql/migrations -COPY --from=builder /build/conf.default.yml . COPY --from=builder /build/conf.prod.yml . COPY --from=builder /build/conf.test.yml . RUN chmod +x capitalizone -CMD ["./capitalizone", "http"] \ No newline at end of file +# API service +CMD ["./zaca", "api"] + +# TLS service +# CMD ["./zaca", "api"] + +# OCSP service +# CMD ["./zaca", "api"] \ No newline at end of file diff --git a/Makefile b/Makefile index 28f830d..6bbf519 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,20 @@ .PHONY: all build clean -GOPROXY=https://goproxy.oneitfarm.com,https://goproxy.cn,direct -PROG=bin/capitalizone +PROG=bin/zaca SRCS=. # git commit hash COMMIT_HASH=$(shell git rev-parse --short HEAD || echo "GitNotFound") -# 编译日期 +# Compilation date BUILD_DATE=$(shell date '+%Y-%m-%d %H:%M:%S') -# 编译条件 +# Compilation conditions CFLAGS = -ldflags "-s -w -X \"main.BuildVersion=${COMMIT_HASH}\" -X \"main.BuildDate=$(BUILD_DATE)\"" all: if [ ! -d "./bin/" ]; then \ mkdir bin; \ fi - GOPROXY=$(GOPROXY) CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(CFLAGS) -o $(PROG) $(SRCS) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(CFLAGS) -o $(PROG) $(SRCS) build: go build -race -tags=jsoniter @@ -33,19 +32,10 @@ test: go run main.go -env test rootca: - go run main.go -env test -envfile ".env.rootca" -rootca - -fake: - go run test/fake/fake_server.go -env test -ca https://192.168.2.80:8381 - -cfssl-model: - gen --sqltype=mysql -c "root:123456@tcp(192.168.2.80:3306)/cap?charset=utf8mb4&parseTime=True&loc=Local" -d cap --json --generate-dao --overwrite --gorm --db --module "gitlab.oneitfarm.com/bifrost/capitalizone/examples/cfssl-model" --out ./examples/cfssl-model - -telegraf: - sudo docker run --network=host -v `pwd`/telegraf.conf:/telegraf.conf --rm -it telegraf:1.19.0 telegraf --config /telegraf.conf + go run main.go -env test -envfile ".env.example" -rootca migration: - go run main.go -envfile ".env.prod" + go run main.go -envfile ".env.example" clean: rm -rf ./bin \ No newline at end of file diff --git a/README.md b/README.md index 09deab6..9b5d9b4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,188 @@ -# CApitaliZone +# ZACA + +Zaca is a Ca pkitls toolkit developed based on cloudflare cfssl + +Zaca includes the following components: + +1. TLS service, as the CA center, is used for certificate issuance, revocation, signature and other operations. +2. API services, as some API services for certificate management. +2. OCSP service is a service that queries the online status of certificates and has been signed by OCSP. +2. SDK component, which is used for other services to access the CA SDK as a toolkit for certificate issuance and automatic rotation. + +### Building + +Building cfssl requires a [working Go 1.12+ installation](http://golang.org/doc/install). + +``` +$ git clone git@github.com:ztalab/ZACA.git +$ cd ZACA +$ make +``` + +You can set GOOS and GOARCH environment variables to allow Go to cross-compile alternative platforms. + +The resulting binaries will be in the bin folder: + +``` +$ tree bin +bin +├── zaca + +0 directories, 1 files +``` + +### Configuration reference + +Zaca configuration can be set through environment variables or configured through configuration files. The priority of environment variables is higher than that of configuration files + +Environment variable configuration reference: + +``` +IS_ENV:test +# Timing configuration +IS_INFLUXDB_ENABLED: true +IS_INFLUXDB_ADDRESS: 127.0.0.1 +IS_INFLUXDB_DATABASE: victoria +IS_INFLUXDB_PASSWORD: victoria +IS_INFLUXDB_PORT: "8427" +IS_INFLUXDB_READ_PASSWORD: victoria +IS_INFLUXDB_READ_USERNAME: victoria +IS_INFLUXDB_USERNAME: victoria +# Self certificate configuration +IS_KEYMANAGER_CSR_TEMPLATES_INTERMEDIATE_CA_O: site +IS_KEYMANAGER_CSR_TEMPLATES_INTERMEDIATE_CA_OU: spiffe://spiffeid/cluster +# Self signed configuration +IS_KEYMANAGER_SELF_SIGN=false +# Parent CA address +IS_KEYMANAGER_UPPER_CA: https://rootca-tls:8081 +// Log hook address +IS_LOG_LOG_PROXY_HOST: redis-host +IS_LOG_LOG_PROXY_PORT: 6379 +# Database mysql address +IS_MYSQL_DSN: root:root@tcp(127.0.0.1:3306)/cap?charset=utf8mb4&parseTime=True&loc=Local +# OCSP cache time in seconds +IS_OCSP_CACHE_TIME: 60 +# Certificate issuance configuration +IS_SINGLECA_CONFIG_PATH: /etc/capitalizone/config.json +# Confidential storage configuration +IS_VAULT_ADDR: http://127.0.0.1:8200 +IS_VAULT_ENABLED: "false" +IS_VAULT_INIT: "true" +IS_VAULT_PREFIX: ca/ +``` + +### Service Installation + +#### TLS service + +TLS service is used to issue certificates through control`IS_KEYMANAGER_SELF_SIGN` Environment variable to control whether to start as root ca. + +- Started as root Ca, TLS service will self sign certificate. +- When starting as an intermediate Ca, the TLS service needs to request the root CA signing certificate as its own CA certificate. + +Start command:`zaca tls`,Default listening port 8081 + +#### OCSP service + +OCSP online certificate status is used to query the certificate status information. OCSP returns the certificate online status information to quickly check whether the certificate has expired, whether it has been revoked and so on. +Start command:`zaca ocsp`,Default listening port 8082 + +#### API services + +Provide CA center API service, which can be accessed after the service is started`http://localhost:8080/swagger/index.html`,View API documentation. + +Start command:`zaca api`,Default listening port 8080 + + + +#### SDK Installation + +``` +$ go get github.com:ztalab/ZACA +``` + +Then in your Go app you can do something like + +##### Server + +```go +import ( + "github.com/pkg/errors" + "github.com/ztalab/ZACA/pkg/caclient" + "github.com/ztalab/ZACA/pkg/spiffe" +) + +// mTLS Server Use example +func NewMTLSServer() error { + // role: default + // CA Server Address,eg: https://zaca-tls.msp:8081 + // Ocsp Server Address, eg: http://zaca-ocsp:8082 + // CA Auth Key + c := caclient.NewCAI( + caclient.WithCAServer(caclient.RoleDefault, *caAddr), + aclient.WithOcspAddr(*ocspAddr), + caclient.WithSignAlgo(keygen.Sm2SigAlg), + caclient.WithAuthKey(authKey), + ) + // Fill in workload parameters + serverEx, err := c.NewExchanger(&spiffe.IDGIdentity{ + SiteID: "test_site", + ClusterID: "cluster_test", + UniqueID: "client1", + }) + if err != nil { + return errors.Wrap(err, "Exchanger initialization failed") + } + // Obtain tls.Config + tlsCfg, err := serverEx.ServerTLSConfig() + go func() { + // Handle with tls.Config + httpsServer(tlsCfg) + }() + // Start certificate rotation + go serverEx.RotateController().Run() + return nil +} +``` + +#### Client + +```go +import ( + "github.com/pkg/errors" + "github.com/ztalab/ZACA/pkg/caclient" + "github.com/ztalab/ZACA/pkg/spiffe" +) + +// mTLS Client Use example +func NewMTLSClient() (*http.Client, error) { +// role: default +// CA Server Address,eg: https://zaca-tls.msp:8081 +// Ocsp Server Address, eg: http://zaca-ocsp:8082 +// CA Auth Key + c := caclient.NewCAI( + caclient.WithCAServer(caclient.RoleDefault, *caAddr), + aclient.WithOcspAddr(*ocspAddr), + caclient.WithAuthKey(authKey), + caclient.WithSignAlgo(keygen.Sm2SigAlg), + ) + // Fill in workload parameters + serverEx, err := c.NewExchanger(&spiffe.IDGIdentity{ + SiteID: "test_site", + ClusterID: "cluster_test", + UniqueID: "client1", + }) + if err != nil { + return nil, errors.Wrap(err, "Exchanger initialization failed") + } + // Obtain tls.Config + // Server Name It can be '', which is not filled in by default for inter service calls + tlsCfg, err := serverEx.ClientTLSConfig("") + // Handle With tls.Config + client := httpClient(tlsCfg) + // Start certificate rotation + go serverEx.RotateController().Run() + return client, nil +} +``` diff --git a/api/helper/api_helper.go b/api/helper/api_helper.go index 8e46ee5..abe98e9 100644 --- a/api/helper/api_helper.go +++ b/api/helper/api_helper.go @@ -7,7 +7,7 @@ import ( "time" "github.com/gin-gonic/gin" - logger "gitlab.oneitfarm.com/bifrost/cilog/v2" + "github.com/ztalab/ZACA/pkg/logger" ) const ( diff --git a/api/router.go b/api/router.go index f0b6569..6636ac6 100644 --- a/api/router.go +++ b/api/router.go @@ -6,14 +6,13 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/v1/ca" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/v1/certleaf" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/v1/health" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/v1/vault" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/v1/workload" - "gitlab.oneitfarm.com/bifrost/capitalizone/core" - "gitlab.oneitfarm.com/bifrost/capitalizone/docs" + "github.com/ztalab/ZACA/api/helper" + "github.com/ztalab/ZACA/api/v1/ca" + "github.com/ztalab/ZACA/api/v1/certleaf" + "github.com/ztalab/ZACA/api/v1/health" + "github.com/ztalab/ZACA/api/v1/workload" + "github.com/ztalab/ZACA/core" + "github.com/ztalab/ZACA/docs" ) func Serve() *gin.Engine { @@ -44,7 +43,7 @@ func Serve() *gin.Engine { prefix.GET("/cert", helper.WrapH(handler.CertDetail)) prefix.GET("/units_forbid_query", helper.WrapH(handler.UnitsForbidQuery)) prefix.GET("/units_certs_list", helper.WrapH(handler.UnitsCertsList)) - // Root CA 禁止操作 + // Root CA Prohibit operation if !core.Is.Config.Keymanager.SelfSign { lifeCyclePrefix := prefix.Group("/lifecycle") { @@ -78,16 +77,5 @@ func Serve() *gin.Engine { prefix.GET("/cert_chain", helper.WrapH(handler.CertChain)) prefix.GET("/cert_chain_from_root", helper.WrapH(handler.CertChainFromRoot)) } - - { - // Vault - prefix := v1.Group("/vault") - prefix.GET("/token", helper.WrapH(vault.RootToken)) - } - - //if err := router.Run(core.Is.Config.HTTP.Listen); err != nil { - // v2log.Named("router").Fatalf("listen err: %v", err) - // return err - //} return router } diff --git a/api/v1/ca/ca.go b/api/v1/ca/ca.go index 7934f30..a8dbc6f 100644 --- a/api/v1/ca/ca.go +++ b/api/v1/ca/ca.go @@ -1,10 +1,10 @@ package ca import ( - v2log "gitlab.oneitfarm.com/bifrost/cilog/v2" + "github.com/ztalab/ZACA/pkg/logger" "go.uber.org/zap" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/ca" + logic "github.com/ztalab/ZACA/logic/ca" ) type API struct { @@ -14,7 +14,7 @@ type API struct { func NewAPI() *API { return &API{ - logger: v2log.Named("api").SugaredLogger, + logger: logger.Named("api").SugaredLogger, logic: logic.NewLogic(), } } diff --git a/api/v1/ca/config.go b/api/v1/ca/config.go index 5b7e2bf..6d65138 100644 --- a/api/v1/ca/config.go +++ b/api/v1/ca/config.go @@ -3,21 +3,21 @@ package ca import ( "strings" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/ca" + "github.com/ztalab/ZACA/api/helper" + logic "github.com/ztalab/ZACA/logic/ca" ) func init() { - // 载入类型... + // Load type... logic.DoNothing() } -// RoleProfiles 环境隔离类型 +// RoleProfiles Environmental isolation type // @Tags CA -// @Summary (p1)环境隔离类型 -// @Description 环境隔离类型 +// @Summary (p1)Environmental isolation type +// @Description Environmental isolation type // @Produce json -// @Param short query bool false "只返回类型列表, 供搜索条件" +// @Param short query bool false "Only a list of types is returned for search criteria" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=logic.RoleProfile} " " // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=[]string} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse diff --git a/api/v1/ca/intermediate.go b/api/v1/ca/intermediate.go index e7aba72..d6d452c 100644 --- a/api/v1/ca/intermediate.go +++ b/api/v1/ca/intermediate.go @@ -1,19 +1,19 @@ package ca import ( - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/ca" + "github.com/ztalab/ZACA/api/helper" + logic "github.com/ztalab/ZACA/logic/ca" ) func init() { - // 载入类型... + // load type... logic.DoNothing() } -// IntermediateTopology 子CA拓扑 +// IntermediateTopology Sub-CA topology // @Tags CA -// @Summary 子CA拓扑 -// @Description 子CA拓扑 +// @Summary Sub-CA topology +// @Description Sub-CA topology // @Produce json // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=[]logic.IntermediateObject} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse @@ -23,10 +23,10 @@ func (a *API) IntermediateTopology(c *helper.HTTPWrapContext) (interface{}, erro return a.logic.IntermediateTopology() } -// UpperCaIntermediateTopology 上层CA拓扑 +// UpperCaIntermediateTopology Upper CA topology // @Tags CA -// @Summary 上层CA拓扑 -// @Description 上层CA拓扑 +// @Summary Upper CA topology +// @Description Upper CA topology // @Produce json // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=[]logic.IntermediateObject} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse diff --git a/api/v1/ca/overall.go b/api/v1/ca/overall.go index f23aab0..2edc10f 100644 --- a/api/v1/ca/overall.go +++ b/api/v1/ca/overall.go @@ -5,18 +5,18 @@ import ( "time" "github.com/pkg/errors" - v2log "gitlab.oneitfarm.com/bifrost/cilog/v2" + "github.com/ztalab/ZACA/pkg/logger" "gorm.io/gorm" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - "gitlab.oneitfarm.com/bifrost/capitalizone/core" - "gitlab.oneitfarm.com/bifrost/capitalizone/database/mysql/cfssl-model/model" + "github.com/ztalab/ZACA/api/helper" + "github.com/ztalab/ZACA/core" + "github.com/ztalab/ZACA/database/mysql/cfssl-model/model" ) type OverallCertsCountItem struct { - Role string `json:"role"` // 类别 - Total int64 `json:"total"` // 证书总数 - UnitsCount int64 `json:"units_count"` // 服务数量 + Role string `json:"role"` + Total int64 `json:"total"` // Total number of certificates + UnitsCount int64 `json:"units_count"` // number of services } type OverallCertsCountResponse struct { @@ -24,10 +24,10 @@ type OverallCertsCountResponse struct { Certs []OverallCertsCountItem `json:"certs"` } -// OverallCertsCount 证书分类 +// OverallCertsCount Certificate classification // @Tags CA -// @Summary (p2)证书分类 -// @Description 证书总数、根据分类划分的数量、对应服务数量 +// @Summary (p2)Certificate classification +// @Description Total number of certificates, number by classification, number of corresponding services // @Produce json // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=OverallCertsCountResponse} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse @@ -48,8 +48,8 @@ func (a *API) OverallCertsCount(c *helper.HTTPWrapContext) (interface{}, error) roleProfiles, err := a.logic.RoleProfiles() if err != nil { - a.logger.Errorf("获取 role profiles 错误: %s", err) - return nil, errors.New("获取 role profiles 错误") + a.logger.Errorf("Error getting role profiles: %s", err) + return nil, errors.New("Error getting role profiles") } res := &OverallCertsCountResponse{ @@ -86,10 +86,10 @@ type OverallExpiryCertsResponse struct { ExpiryCerts []OverallExpiryGroup `json:"expiry_certs"` } -// OverallExpiryCerts 证书有效期 +// OverallExpiryCerts Certificate validity // @Tags CA -// @Summary (p2)证书有效期 -// @Description 证书已过期数量, 一周内过期数量, 1/3个月内过期数量, 3个月后过期数量 +// @Summary (p2)Certificate validity +// @Description Number of certificates expired: within one week, within 1/3 months and after 3 months // @Produce json // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=OverallExpiryCertsResponse} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse @@ -113,7 +113,7 @@ func (a *API) OverallExpiryCerts(c *helper.HTTPWrapContext) (interface{}, error) ExpiryCerts: make([]OverallExpiryGroup, 0), } - // 一周内 + // Within a week { item := OverallExpiryGroup{Name: "1w"} count, err := getExpiryCountByDuration(7*24*time.Hour, time.Now()) @@ -124,7 +124,7 @@ func (a *API) OverallExpiryCerts(c *helper.HTTPWrapContext) (interface{}, error) res.ExpiryCerts = append(res.ExpiryCerts, item) } - // 一个月内 + // Within one month { item := OverallExpiryGroup{Name: "1m"} count, err := getExpiryCountByDuration(30*24*time.Hour, time.Now()) @@ -135,7 +135,7 @@ func (a *API) OverallExpiryCerts(c *helper.HTTPWrapContext) (interface{}, error) res.ExpiryCerts = append(res.ExpiryCerts, item) } - // 三个月内 + // Within three months { item := OverallExpiryGroup{Name: "3m"} count, err := getExpiryCountByDuration(3*30*24*time.Hour, time.Now()) @@ -146,7 +146,7 @@ func (a *API) OverallExpiryCerts(c *helper.HTTPWrapContext) (interface{}, error) res.ExpiryCerts = append(res.ExpiryCerts, item) } - // 三个月后 + // Three months later { item := OverallExpiryGroup{Name: "3m+"} count, err := getExpiryCountByDuration(999*30*24*time.Hour, time.Now().AddDate(0, 3, 0)) @@ -161,9 +161,9 @@ func (a *API) OverallExpiryCerts(c *helper.HTTPWrapContext) (interface{}, error) } func getExpiryCountByDuration(period time.Duration, before time.Time) (int64, error) { - // 一周内 - // 过期时间 - 当前时间 <= 一周 - // 过期时间 <= 当前时间 + 一周 + // Within a week + // Expiration time - current time < = one week + // Expiration time < = current time + one week expiryDate := time.Now().Add(period) query := core.Is.Db.Session(&gorm.Session{}).Model(&model.Certificates{}). Where("expiry > ?", before). @@ -173,7 +173,7 @@ func getExpiryCountByDuration(period time.Duration, before time.Time) (int64, er var count int64 if err := query.Count(&count).Error; err != nil { - v2log.Errorf("mysql query err: %s", err) + logger.Errorf("mysql query err: %s", err) return 0, err } @@ -190,10 +190,10 @@ type OverallUnitsEnableStatus struct { Disable OverallUnitsEnableItem `json:"disable"` } -// OverallUnitsEnableStatus 启用情况 +// OverallUnitsEnableStatus Enabling condition // @Tags CA -// @Summary (p2)启用情况 -// @Description 已启用总数, 禁用总数, 对应服务数 +// @Summary (p2)Enabling condition +// @Description Total enabled, total disabled, corresponding services // @Produce json // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=OverallUnitsEnableStatus} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse diff --git a/api/v1/ca/workload_topo.go b/api/v1/ca/workload_topo.go index 29cff4c..343b338 100644 --- a/api/v1/ca/workload_topo.go +++ b/api/v1/ca/workload_topo.go @@ -1,24 +1,24 @@ package ca import ( - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/ca" + "github.com/ztalab/ZACA/api/helper" + logic "github.com/ztalab/ZACA/logic/ca" ) func init() { - // 载入类型... + // load type... logic.DoNothing() } -// WorkloadUnits CA 下 Units -// 以 UniqueID 为单元 +// WorkloadUnits CA Units +// UniqueID as unit // @Tags CA -// @Summary (p1)服务单元 -// @Description CA 下 Units +// @Summary (p1)Service unit +// @Description CA Units // @Produce json -// @Param page query int false "页数, 默认1" -// @Param limit_num query int false "页数限制, 默认20" -// @Param unique_id query string false "UniqueID 查询" +// @Param page query int false "Number of pages, default 1" +// @Param limit_num query int false "Page limit, default 20" +// @Param unique_id query string false "UniqueID Query" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=helper.MSPNormalizeList{list=[]logic.WorkloadUnit}} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse diff --git a/api/v1/certleaf/base.go b/api/v1/certleaf/base.go index ed928fb..39db2ee 100644 --- a/api/v1/certleaf/base.go +++ b/api/v1/certleaf/base.go @@ -1,10 +1,10 @@ package certleaf import ( - v2log "gitlab.oneitfarm.com/bifrost/cilog/v2" + "github.com/ztalab/ZACA/pkg/logger" "go.uber.org/zap" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/certleaf" + logic "github.com/ztalab/ZACA/logic/certleaf" ) type API struct { @@ -14,7 +14,7 @@ type API struct { func NewAPI() *API { return &API{ - logger: v2log.Named("api").SugaredLogger, + logger: logger.Named("api").SugaredLogger, logic: logic.NewLogic(), } } diff --git a/api/v1/certleaf/cert_chain.go b/api/v1/certleaf/cert_chain.go index a31adda..0e47de6 100644 --- a/api/v1/certleaf/cert_chain.go +++ b/api/v1/certleaf/cert_chain.go @@ -3,20 +3,20 @@ package certleaf import ( "errors" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - caLogic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/ca" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/certleaf" - "gitlab.oneitfarm.com/bifrost/capitalizone/logic/schema" + "github.com/ztalab/ZACA/api/helper" + caLogic "github.com/ztalab/ZACA/logic/ca" + logic "github.com/ztalab/ZACA/logic/certleaf" + "github.com/ztalab/ZACA/logic/schema" ) -// CertChain 证书链 +// CertChain Certificate chain // @Tags certleaf // @Summary (p1)CertChain -// @Description 获取证书链信息 +// @Description Get certificate chain information // @Produce json -// @Param self_cert query bool false "展示 CA 自身证书链" -// @Param sn query string false "SN+AKI 查询指定证书" -// @Param aki query string false "SN+AKI 查询指定证书" +// @Param self_cert query bool false "Show CA's own certificate chain" +// @Param sn query string false "SN+AKI Query the specified certificate" +// @Param aki query string false "SN+AKI Query the specified certificate" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=logic.LeafCert} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse @@ -37,10 +37,10 @@ type RootCertChains struct { Root *caLogic.IntermediateObject `json:"root"` } -// CertChainFromRoot Root视角下所有证书链 +// CertChainFromRoot All certificate chains from the root Perspective // @Tags certleaf -// @Summary (p1)根视角证书链 -// @Description Root视角下所有证书链 +// @Summary (p1)Root view certificate chain +// @Description All certificate chains from the root Perspective // @Produce json // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=RootCertChains} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse @@ -68,7 +68,7 @@ func (a *API) CertChainFromRoot(c *helper.HTTPWrapContext) (interface{}, error) children, err := caLogic.NewLogic().UpperCaIntermediateTopology() if err != nil { - a.logger.Errorf("获取上层 CA 拓扑结构错误: %s", err) + a.logger.Errorf("Error getting upper CA topology: %s", err) } chain.Root.Children = children diff --git a/api/v1/health/health.go b/api/v1/health/health.go index c2b611e..ed3d7cd 100644 --- a/api/v1/health/health.go +++ b/api/v1/health/health.go @@ -2,21 +2,13 @@ package health import ( "crypto/tls" - "fmt" - "log" "net/http" - "os" "time" - "github.com/hashicorp/go-discover" - "github.com/hashicorp/go-discover/provider/k8s" - vaultAPI "github.com/hashicorp/vault/api" - - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - "gitlab.oneitfarm.com/bifrost/capitalizone/ca/keymanager" - "gitlab.oneitfarm.com/bifrost/capitalizone/core" - cfClient "gitlab.oneitfarm.com/bifrost/cfssl/api/client" - "gitlab.oneitfarm.com/bifrost/cfssl/hook" + "github.com/ztalab/ZACA/api/helper" + "github.com/ztalab/ZACA/ca/keymanager" + "github.com/ztalab/ZACA/core" + cfClient "github.com/ztalab/cfssl/api/client" ) // CfsslHealthAPI ... @@ -59,19 +51,6 @@ func Health(c *helper.HTTPWrapContext) (interface{}, error) { } hm = append(hm, module) } - //{ - // // VictoriaMetrics - // module := &HealthModule{ - // Name: "VictoriaMetrics", - // DisplayName: "VictoriaMetrics", - // State: 200, - // } - // if _, _, err := core.Is.Metrics.InfluxDBHttpClient.Client.Ping(2 * time.Second); err != nil { - // module.Message = err.Error() - // module.State = 500 - // } - // hm = append(hm, module) - //} { // RootCA module := &HealthModule{ @@ -94,57 +73,5 @@ func Health(c *helper.HTTPWrapContext) (interface{}, error) { }) hm = append(hm, module) } - if hook.EnableVaultStorage { - // Vault - module := &HealthModule{ - Name: "Vault", - DisplayName: "Vault", - State: 200, - } - d := discover.Discover{ - Providers: map[string]discover.Provider{ - "k8s": &k8s.Provider{}, - }, - } - // use ioutil.Discard for no log output - l := log.New(os.Stderr, "", log.LstdFlags) - addrs, err := d.Addrs(core.Is.Config.Vault.Discover, l) - if err != nil { - module.State = 500 - module.Message = fmt.Sprintf("Vault K8s IP 发现失败: %s", err) - } else { - if len(addrs) == 0 { - module.State = 500 - module.Message = "Vault K8s 节点不可用" - } else { - for _, addr := range addrs { - conf := &vaultAPI.Config{ - Address: "http://" + addr + ":8200", - HttpClient: httpClient, - } - cli, _ := vaultAPI.NewClient(conf) - cli.SetToken(core.Is.Config.Vault.Token) - var retryTimes int - RETRY: - status, err := cli.Sys().SealStatus() - if err != nil { - if retryTimes == 0 { - retryTimes++ - goto RETRY - } - module.State = 500 - module.Message += fmt.Sprintf("Vault 节点 %s 获取 seal status 错误: %s\n", addr, err) - } else { - if status.Sealed { - module.State = 500 - module.Message += fmt.Sprintf("Vault 节点 %s 未解封\n", addr) - module.Desc = "未解封可能原因是 K8s Node 节点异常重启" - } - } - } - } - } - hm = append(hm, module) - } return hm, nil } diff --git a/api/v1/vault/vault.go b/api/v1/vault/vault.go deleted file mode 100644 index 29c71fb..0000000 --- a/api/v1/vault/vault.go +++ /dev/null @@ -1,50 +0,0 @@ -package vault - -import ( - "errors" - "os" - - vaultAPI "github.com/hashicorp/vault/api" - jsoniter "github.com/json-iterator/go" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - "gitlab.oneitfarm.com/bifrost/capitalizone/core" - "gitlab.oneitfarm.com/bifrost/capitalizone/database/mysql/cfssl-model/model" - "gitlab.oneitfarm.com/bifrost/capitalizone/pkg/vaultinit" - logger "gitlab.oneitfarm.com/bifrost/cilog/v2" - "gorm.io/gorm" -) - -// RootToken ... -func RootToken(c *helper.HTTPWrapContext) (interface{}, error) { - verifyKey := os.Getenv("IS_VAULT_VERIFY_KEY") - if verifyKey == "" { - return nil, errors.New("verify key not found") - } - - if c.G.Query("verify_key") != verifyKey { - return nil, errors.New("verify key error") - } - - envRootToken := core.Is.Config.Vault.Token - if envRootToken != "" { - return envRootToken, nil - } - - keyPair := &model.SelfKeypair{} - if err := core.Is.Db.Where("name = ?", vaultinit.StoreKeyName).Order("id desc").First(keyPair).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - logger.Errorf("Vault key not found") - return nil, errors.New("vault key not found") - } - logger.Errorf("DB query err: %s", err) - return nil, err - } - key := keyPair.PrivateKey.String - keys := new(vaultAPI.InitResponse) - if err := jsoniter.UnmarshalFromString(key, keys); err != nil { - logger.Errorf("Unmarshal keys err: %s", err) - return nil, err - } - - return keys.RootToken, nil -} diff --git a/api/v1/workload/lifecycle.go b/api/v1/workload/lifecycle.go index 4859e24..1f710dc 100644 --- a/api/v1/workload/lifecycle.go +++ b/api/v1/workload/lifecycle.go @@ -1,17 +1,17 @@ -// Package workload 证书生命周期管理 +// Package workload Certificate Lifecycle Management package workload import ( - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/workload" + "github.com/ztalab/ZACA/api/helper" + logic "github.com/ztalab/ZACA/logic/workload" ) -// RevokeCerts 吊销证书 +// RevokeCerts revoked certificate // @Tags Workload // @Summary (p3)Revoke -// @Description 吊销证书 +// @Description revoked certificate // @Produce json -// @Param body body logic.RevokeCertsParams true "sn+aki / unique_id 二选一" +// @Param body body logic.RevokeCertsParams true "sn+aki / unique_id pick one of two" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse @@ -28,12 +28,12 @@ func (a *API) RevokeCerts(c *helper.HTTPWrapContext) (interface{}, error) { return "revoked", nil } -// RecoverCerts 恢复证书 +// RecoverCerts Restore certificate // @Tags Workload // @Summary (p3)Recover -// @Description 恢复证书 +// @Description Restore certificate // @Produce json -// @Param body body logic.RecoverCertsParams true "sn+aki / unique_id 二选一" +// @Param body body logic.RecoverCertsParams true "sn+aki / unique_id either-or" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse @@ -50,10 +50,10 @@ func (a *API) RecoverCerts(c *helper.HTTPWrapContext) (interface{}, error) { return "recovered", nil } -// ForbidNewCerts 禁止某个 UniqueID 申请证书 +// ForbidNewCerts Prohibit a uniqueID from requesting a certificate // @Tags Workload -// @Summary 禁止申请证书 -// @Description 禁止某个 UniqueID 申请证书 +// @Summary Application for certificate is prohibited +// @Description Prohibit a uniqueID from requesting a certificate // @Produce json // @Param body body logic.ForbidNewCertsParams true " " // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody " " @@ -72,10 +72,10 @@ func (a *API) ForbidNewCerts(c *helper.HTTPWrapContext) (interface{}, error) { return "success", nil } -// RecoverForbidNewCerts 恢复允许某个 UniqueID 申请证书 +// RecoverForbidNewCerts Recovery allows a uniqueID to request a certificate // @Tags Workload -// @Summary 恢复申请证书 -// @Description 恢复允许某个 UniqueID 申请证书 +// @Summary Resume application certificate +// @Description Recovery allows a uniqueID to request a certificate // @Produce json // @Param body body logic.ForbidNewCertsParams true " " // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody " " @@ -98,10 +98,10 @@ type ForbidUnitParams struct { UniqueID string `json:"unique_id" binding:"required"` } -// ForbidUnit 吊销并禁止服务证书 +// ForbidUnit Revoke and prohibit service certificates // @Tags Workload -// @Summary (p1)吊销并禁止服务证书 -// @Description 吊销并禁止服务证书 +// @Summary (p1)Revoke and prohibit service certificates +// @Description Revoke and prohibit service certificates // @Produce json // @Param json body ForbidUnitParams true " " // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody " " @@ -116,26 +116,26 @@ func (a *API) ForbidUnit(c *helper.HTTPWrapContext) (interface{}, error) { UniqueIds: []string{req.UniqueID}, }) if err != nil { - a.logger.With("req", req).Errorf("禁止申请证书失败: %s", err) + a.logger.With("req", req).Errorf("Failed to prohibit certificate application: %s", err) return nil, err } - // 2021.04.15 (功能性调整) 证书启用/禁用影响证书通信 OCSP 认证、Sidecar mTLS 使用,不会吊销证书 + // 2021.04.15 (functional adjustment) certificate enabling and disabling will affect certificate communication, OCSP authentication and MTLs use, and the certificate will not be revoked // err = a.logic.RevokeCerts(&logic.RevokeCertsParams{ // UniqueId: req.UniqueID, // }) // if err != nil { - // a.logger.With("req", req).Errorf("吊销服务证书失败: %s", err) + // a.logger.With("req", req).Errorf("Revocation of service certificate failed: %s", err) // return nil, err // } return "success", nil } -// RecoverUnit 恢复并允许服务证书 +// RecoverUnit Restore and allow service certificates // @Tags Workload -// @Summary (p1)恢复并允许服务证书 -// @Description 恢复并允许服务证书 +// @Summary (p1)Restore and allow service certificates +// @Description Restore and allow service certificates // @Produce json // @Param json body ForbidUnitParams true " " // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody " " @@ -150,7 +150,7 @@ func (a *API) RecoverUnit(c *helper.HTTPWrapContext) (interface{}, error) { UniqueIds: []string{req.UniqueID}, }) if err != nil { - a.logger.With("req", req).Errorf("恢复申请证书失败: %s", err) + a.logger.With("req", req).Errorf("Failed to restore the requested certificate: %s", err) return nil, err } @@ -158,7 +158,7 @@ func (a *API) RecoverUnit(c *helper.HTTPWrapContext) (interface{}, error) { // UniqueId: req.UniqueID, // }) // if err != nil { - // a.logger.With("req", req).Errorf("恢复服务证书失败: %s", err) + // a.logger.With("req", req).Errorf("Failed to restore service certificate: %s", err) // return nil, err // } diff --git a/api/v1/workload/workload.go b/api/v1/workload/workload.go index b8deef0..337611d 100644 --- a/api/v1/workload/workload.go +++ b/api/v1/workload/workload.go @@ -7,16 +7,16 @@ import ( "github.com/araddon/dateparse" "github.com/pkg/errors" "github.com/tal-tech/go-zero/core/fx" - v2log "gitlab.oneitfarm.com/bifrost/cilog/v2" + "github.com/ztalab/ZACA/pkg/logger" "go.uber.org/zap" "gorm.io/gorm" - "gitlab.oneitfarm.com/bifrost/capitalizone/api/helper" - "gitlab.oneitfarm.com/bifrost/capitalizone/core" - "gitlab.oneitfarm.com/bifrost/capitalizone/database/mysql/cfssl-model/dao" - "gitlab.oneitfarm.com/bifrost/capitalizone/database/mysql/cfssl-model/model" - "gitlab.oneitfarm.com/bifrost/capitalizone/logic/schema" - logic "gitlab.oneitfarm.com/bifrost/capitalizone/logic/workload" + "github.com/ztalab/ZACA/api/helper" + "github.com/ztalab/ZACA/core" + "github.com/ztalab/ZACA/database/mysql/cfssl-model/dao" + "github.com/ztalab/ZACA/database/mysql/cfssl-model/model" + "github.com/ztalab/ZACA/logic/schema" + logic "github.com/ztalab/ZACA/logic/workload" ) type API struct { @@ -27,31 +27,30 @@ type API struct { func NewAPI() *API { return &API{ logic: logic.NewLogic(), - logger: v2log.Named("api").SugaredLogger, + logger: logger.Named("api").SugaredLogger, } } -// CertList 证书列表 +// CertList Certificate list // @Tags Workload // @Summary (p3)List -// @Description 证书列表 +// @Description Certificate list // @Produce json -// @Param role query string false "证书类型 gateway/sidecar/standalone" -// @Param unique_id query string false "根据UniqueID查询" -// @Param cert_sn query string false "根据证书序列号查询" -// @Param status query string false "证书状态 good/revoked" -// @Param order query string false "排序,默认 issued_at desc" -// @Param expiry_start_time query string false "过期, 起始时间点" -// @Param expiry_end_time query string false "过期, 结束时间点" -// @Param limit_num query int false "分页参数, 默认 20" -// @Param page query int false "页数, 默认 1" +// @Param role query string false "Certificate type default" +// @Param unique_id query string false "Query by unique ID" +// @Param cert_sn query string false "Query by certificate serial number" +// @Param status query string false "Certificate status good/revoked" +// @Param order query string false "Sort, default issued_at desc" +// @Param expiry_start_time query string false "Expiration, starting point" +// @Param expiry_end_time query string false "Expiration, end time point" +// @Param limit_num query int false "Paging parameters, default 20" +// @Param page query int false "Number of pages, default 1" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=helper.MSPNormalizeList{list=[]schema.SampleCert}} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse // @Router /workload/certs [get] func (a *API) CertList(c *helper.HTTPWrapContext) (interface{}, error) { var req = struct { - // 查询条件 Role string `form:"role"` UniqueID string `form:"unique_id"` Status string `form:"status"` @@ -91,20 +90,19 @@ func (a *API) CertList(c *helper.HTTPWrapContext) (interface{}, error) { return result, nil } -// CertDetail 证书详情 +// CertDetail Certificate details // @Tags Workload // @Summary Detail -// @Description 证书详情 +// @Description Certificate details // @Produce json -// @Param sn query string true "证书 sn" -// @Param aki query string true "证书 aki" +// @Param sn query string true "Certificate sn" +// @Param aki query string true "Certificate aki" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=schema.FullCert} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse // @Router /workload/cert [get] func (a *API) CertDetail(c *helper.HTTPWrapContext) (interface{}, error) { var req struct { - // 查询条件 SN string `form:"sn" binding:"required"` AKI string `form:"aki" binding:"required"` } @@ -129,19 +127,18 @@ func (a *API) CertDetail(c *helper.HTTPWrapContext) (interface{}, error) { return result, nil } -// UnitsForbidQuery 查询 unique_id 是否被禁止申请证书 +// UnitsForbidQuery Query unique_ Is ID prohibited from applying for certificate // @Tags Workload -// @Summary 禁止申请证书查询 -// @Description 查询 unique_id 是否被禁止申请证书 +// @Summary Prohibit applying for certificate query +// @Description Query unique_id Is it forbidden to apply for certificate // @Produce json -// @Param unique_ids query []string true "查询 unique_id 数组" collectionFormat(multi) +// @Param unique_ids query []string true "Query unique_ID array" collectionFormat(multi) // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=logic.UnitsForbidQueryResult} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse // @Router /workload/units_forbid_query [get] func (a *API) UnitsForbidQuery(c *helper.HTTPWrapContext) (interface{}, error) { var req struct { - // 查询条件 UniqueIds []string `form:"unique_ids" binding:"required"` } c.BindG(&req) @@ -159,12 +156,12 @@ type UnitsStatusReq struct { UniqueIds []string `json:"unique_ids" binding:"required"` } -// UnitsStatus 服务对应状态查询 +// UnitsStatus Service corresponding status query // @Tags Workload -// @Summary (p1)服务对应状态查询 -// @Description 服务对应状态查询 +// @Summary (p1)Service corresponding status query +// @Description Service corresponding status query // @Produce json -// @Param json body UnitsStatusReq true "查询 unique_id 数组" +// @Param json body UnitsStatusReq true "Query unique_ID array" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=object} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse @@ -216,7 +213,7 @@ func (a *API) getUnitsStatus(uniqueIds []string) (UnitsStatusMap, error) { var list []*model.Certificates if err := query.Find(&list).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - a.logger.Errorf("数据库查询错误: %s", err) + a.logger.Errorf("Database query error: %s", err) return nil, err } @@ -245,26 +242,25 @@ type UnitsCertsItem struct { Forbidden bool `json:"forbidden"` } -// UnitsCertsList 服务证书列表 +// UnitsCertsList List of service certificates // Deprecated // @Tags Workload -// @Summary (p1)服务证书列表 -// @Description 服务证书列表 +// @Summary (p1)List of service certificates +// @Description List of service certificates // @Produce json -// @Param unique_id query string false "查询 unique_id" -// @Param role query string false "证书类型" -// @Param expiry_start_time query string false "过期, 起始时间点" -// @Param expiry_end_time query string false "过期, 结束时间点" -// @Param is_forbid query int false "是否禁用, 1禁用 2启用" -// @Param limit_num query int false "分页参数, 默认 20" -// @Param page query int false "页数, 默认 1" +// @Param unique_id query string false "Query unique_id" +// @Param role query string false "Certificate type" +// @Param expiry_start_time query string false "Expiration, starting point" +// @Param expiry_end_time query string false "Expiration, end time point" +// @Param is_forbid query int false "Disable, 1 disable, 2 enable" +// @Param limit_num query int false "Paging parameters, default 20" +// @Param page query int false "Number of pages, default 1" // @Success 200 {object} helper.MSPNormalizeHTTPResponseBody{data=[]UnitsCertsItem} " " // @Failure 400 {object} helper.HTTPWrapErrorResponse // @Failure 500 {object} helper.HTTPWrapErrorResponse // @Router /workload/units_certs_list [get] func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { var req = struct { - // 查询条件 UniqueID string `form:"unique_id"` Role string `form:"role"` ExpiryStartTime string `form:"expiry_start_time"` @@ -298,7 +294,7 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { if req.ExpiryStartTime != "" { date, err := dateparse.ParseAny(req.ExpiryStartTime) if err != nil { - return nil, errors.Wrap(err, "过期起始时间错误") + return nil, errors.Wrap(err, "Expiration start time error") } query = query.Where("expiry > ?", date) expiryStartDate = &date @@ -307,7 +303,7 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { if req.ExpiryEndTime != "" { date, err := dateparse.ParseAny(req.ExpiryEndTime) if err != nil { - return nil, errors.Wrap(err, "过期结束时间错误") + return nil, errors.Wrap(err, "Expiration end time error") } query = query.Where("expiry < ?", date) expiryEndDate = &date @@ -325,7 +321,7 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { if len(uniqueIds) == 0 { list, total, err := dao.GetAllCertificates(query, req.Page, req.LimitNum, "common_name asc") if err != nil { - a.logger.Errorf("数据库查询错误: %s", err) + a.logger.Errorf("Database query error: %s", err) return nil, err } @@ -364,17 +360,17 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { list, _, err := dao.GetAllCertificates(query, 1, 100, "issued_at desc") if err != nil { - a.logger.Errorf("数据库查询错误: %s", err) + a.logger.Errorf("Database query error: %s", err) return nil, err } - a.logger.Debugf("返回证书数量: %v", len(list)) + a.logger.Debugf("Number of returned certificates: %v", len(list)) forbidMap, err := a.logic.UnitsForbidQuery(&logic.UnitsForbidQueryParams{ UniqueIds: uniqueIds, }) if err != nil { - a.logger.Errorf("服务禁止状态查询错误: %s", err) + a.logger.Errorf("Service prohibition status query error: %s", err) return nil, err } @@ -390,7 +386,7 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { fullCert, err := schema.GetFullCertByModelCert(row) if err != nil { - a.logger.Errorf("获取 full cert 错误: %s", err) + a.logger.Errorf("Get full cert error: %s", err) continue } unitsCertsMap[uid].Certs = append(unitsCertsMap[uid].Certs, fullCert) @@ -401,7 +397,7 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { result = append(result, v) } - a.logger.Debugf("返回服务数量: %v", len(result)) + a.logger.Debugf("Return service quantity: %v", len(result)) return helper.MSPNormalizeList{ List: result, @@ -414,9 +410,6 @@ func (a *API) UnitsCertsList(c *helper.HTTPWrapContext) (interface{}, error) { } //func getExpiryCountByDuration(sign string) (before, after time.Time, err error) { -// // 一周内 -// // 过期时间 - 当前时间 <= 一周 -// // 过期时间 <= 当前时间 + 一周 // expiryDate := func(du time.Duration) time.Time { // return time.Now().Add(du) // } diff --git a/api/v1/workload/workload_test.go b/api/v1/workload/workload_test.go deleted file mode 100644 index dccca72..0000000 --- a/api/v1/workload/workload_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package workload - -import ( - "fmt" - "strconv" - "strings" - "testing" - - fuzz "github.com/google/gofuzz" -) - -func TestRandomUniqueIds(t *testing.T) { - fuzzer := fuzz.New() - - var arr []string - for i := 0; i < 5000; i++ { - var str int - fuzzer.Fuzz(&str) - arr = append(arr, strconv.Itoa(str)) - } - - fmt.Println(`""` + strings.Join(arr, `","`) + `""`) -} diff --git a/bin/capitalizone b/bin/capitalizone deleted file mode 100755 index e6ed68f2dedc4935c5adba2d4359299e0efd5529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67731456 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%0Ay8mAgm2B_EEa!TiAY)XtJ9*9NaK75Q(z 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

BuetORSf+Pu9b3r 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