From 251beb24d30ee8a12129f26a00086692b5af4f78 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 21 Feb 2019 17:33:46 +0300 Subject: [PATCH] Added openapi description --- app.go | 2 +- config.go | 33 +++++----- control.go | 24 +------ helpers.go | 29 --------- openapi/openapi.yaml | 148 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 165 insertions(+), 71 deletions(-) diff --git a/app.go b/app.go index 5485b94a..518bb397 100644 --- a/app.go +++ b/app.go @@ -173,7 +173,7 @@ func run(args options) { for { // this is an endless loop httpsServer.cond.L.Lock() // this mechanism doesn't let us through until all conditions are ment - for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until neccessary data is supplied + for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until necessary data is supplied httpsServer.cond.Wait() } address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS)) diff --git a/config.go b/config.go index c1aa2f8d..158381c1 100644 --- a/config.go +++ b/config.go @@ -63,37 +63,34 @@ type dnsConfig struct { var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"} type tlsConfigSettings struct { - Enabled bool `yaml:"enabled" json:"enabled"` - ServerName string `yaml:"server_name" json:"server_name,omitempty"` - ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` - PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` - PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` + Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DOT/DOH/HTTPS) status + ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server + ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect + PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` // HTTPS port. If 0, HTTPS will be disabled + PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled dnsforward.TLSConfig `yaml:",inline" json:",inline"` } // field ordering is not important -- these are for API and are recalculated on each run type tlsConfigStatus struct { - // certificate status - ValidCert bool `yaml:"-" json:"valid_cert"` - ValidChain bool `yaml:"-" json:"valid_chain"` - Subject string `yaml:"-" json:"subject,omitempty"` - Issuer string `yaml:"-" json:"issuer,omitempty"` - NotBefore time.Time `yaml:"-" json:"not_before,omitempty"` - NotAfter time.Time `yaml:"-" json:"not_after,omitempty"` - DNSNames []string `yaml:"-" json:"dns_names"` - StatusCertificate string `yaml:"-" json:"status_cert,omitempty"` + ValidCert bool `yaml:"-" json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates + ValidChain bool `yaml:"-" json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA + Subject string `yaml:"-" json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain + Issuer string `yaml:"-" json:"issuer,omitempty"` // Issuer is the issuer of the first certificate in the chain + NotBefore time.Time `yaml:"-" json:"not_before,omitempty"` // NotBefore is the NotBefore field of the first certificate in the chain + NotAfter time.Time `yaml:"-" json:"not_after,omitempty"` // NotAfter is the NotAfter field of the first certificate in the chain + DNSNames []string `yaml:"-" json:"dns_names"` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain // key status - ValidKey bool `yaml:"-" json:"valid_key"` - KeyType string `yaml:"-" json:"key_type,omitempty"` + ValidKey bool `yaml:"-" json:"valid_key"` // ValidKey is true if the key is a valid private key + KeyType string `yaml:"-" json:"key_type,omitempty"` // KeyType is one of RSA or ECDSA // is usable? set by validator usable bool // warnings - Warning string `yaml:"-" json:"warning,omitempty"` - WarningValidation string `yaml:"-" json:"warning_validation,omitempty"` + WarningValidation string `yaml:"-" json:"warning_validation,omitempty"` // WarningValidation is a validation warning message with the issue description } // field ordering is important -- yaml fields will mirror ordering from here diff --git a/control.go b/control.go index 4d13a9b8..a87bd2cd 100644 --- a/control.go +++ b/control.go @@ -1118,7 +1118,7 @@ func validateCertificates(data tlsConfig) tlsConfig { // clear out status for certificates data.tlsConfigStatus = tlsConfigStatus{} - // check only public certificate separetely from the key + // check only public certificate separately from the key if data.CertificateChain != "" { log.Tracef("got certificate: %s", data.CertificateChain) @@ -1194,24 +1194,6 @@ func validateCertificates(data tlsConfig) tlsConfig { data.NotAfter = notAfter data.NotBefore = mainCert.NotBefore data.DNSNames = mainCert.DNSNames - - data.StatusCertificate = fmt.Sprintf("Certificate expires on %s", notAfter) //, valid for hostname %s", mainCert.NotAfter, mainCert.Subject.CommonName) - if len(mainCert.DNSNames) == 1 { - data.StatusCertificate += fmt.Sprintf(", valid for hostname %s", mainCert.DNSNames[0]) - } else if len(mainCert.DNSNames) > 1 { - data.StatusCertificate += ", valid for hostnames " + strings.Join(mainCert.DNSNames, ", ") - } - - // issue a warning if certificate is about to expire - now := time.Now() - if now.AddDate(0, 0, 30).After(notAfter) { - timeLeft := notAfter.Sub(now) - if timeLeft > 0 { - data.Warning = fmt.Sprintf("Your certificate expires in %.0f days, we recommend you update it soon", timeLeft.Hours()/24) - } else { - data.Warning = fmt.Sprintf("Your certificate has expired on %s, we recommend you update it immediatedly", mainCert.NotAfter) - } - } } } @@ -1322,11 +1304,11 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) { w.Header().Set("Content-Type", "application/json") if data.CertificateChain != "" { encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain)) - data.CertificateChain = string(encoded) + data.CertificateChain = encoded } if data.PrivateKey != "" { encoded := base64.StdEncoding.EncodeToString([]byte(data.PrivateKey)) - data.PrivateKey = string(encoded) + data.PrivateKey = encoded } err := json.NewEncoder(w).Encode(data) if err != nil { diff --git a/helpers.go b/helpers.go index cf2d3598..822c77df 100644 --- a/helpers.go +++ b/helpers.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" "net" "net/http" "net/url" @@ -257,34 +256,6 @@ func checkPacketPortAvailable(host string, port int) error { return err } -// ------------------------ -// random string generation -// ------------------------ -const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -const ( - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 0; { - if remain == 0 { - cache, remain = rand.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - - return string(b) -} - // --------------------- // debug logging helpers // --------------------- diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index c9801b4b..452e5036 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -2,7 +2,7 @@ swagger: '2.0' info: title: 'AdGuard Home' description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.' - version: 0.92.0 + version: 0.93.0 schemes: - http basePath: /control @@ -12,6 +12,9 @@ tags: - name: global description: 'AdGuard Home server general settings and controls' + - + name: tls + description: 'AdGuard Home HTTPS/DOH/DOT settings' - name: log description: 'AdGuard Home query log' @@ -267,6 +270,70 @@ paths: 200: description: OK + # -------------------------------------------------- + # TLS server methods + # -------------------------------------------------- + + /tls/status: + get: + tags: + - tls + operationId: tlsStatus + summary: "Returns TLS configuration and its status" + responses: + 200: + description: OK + schema: + $ref: "#/definitions/TlsConfig" + + /tls/configure: + post: + tags: + - tls + operationId: tlsConfigure + summary: "Updates current TLS configuration" + consumes: + - application/json + parameters: + - in: "body" + name: "body" + description: "TLS configuration JSON" + required: true + schema: + $ref: "#/definitions/TlsConfig" + responses: + 200: + description: "TLS configuration and its status" + schema: + $ref: "#/definitions/TlsConfig" + 400: + description: "Invalid configuration or unavailable port" + 500: + description: "Error occurred while applying configuration" + + /tls/validate: + post: + tags: + - tls + operationId: tlsValidate + summary: "Checks if the current TLS configuration is valid" + consumes: + - application/json + parameters: + - in: "body" + name: "body" + description: "TLS configuration JSON" + required: true + schema: + $ref: "#/definitions/TlsConfig" + responses: + 200: + description: "TLS configuration and its status" + schema: + $ref: "#/definitions/TlsConfig" + 400: + description: "Invalid configuration or unavailable port" + # -------------------------------------------------- # DHCP server methods # -------------------------------------------------- @@ -1063,4 +1130,81 @@ definitions: type: "array" description: "Query log" items: - $ref: "#/definitions/QueryLogItem" \ No newline at end of file + $ref: "#/definitions/QueryLogItem" + TlsConfig: + type: "object" + description: "TLS configuration settings and status" + properties: + # TLS configuration + enabled: + type: "boolean" + example: "true" + description: "enabled is the encryption (DOT/DOH/HTTPS) status" + server_name: + type: "string" + example: "example.org" + description: "server_name is the hostname of your HTTPS/TLS server" + force_https: + type: "boolean" + example: "true" + description: "if true, forces HTTP->HTTPS redirect" + port_https: + type: "integer" + format: "int32" + example: 443 + description: "HTTPS port. If 0, HTTPS will be disabled." + port_dns_over_tls: + type: "integer" + format: "int32" + example: 853 + description: "DNS-over-TLS port. If 0, DOT will be disabled." + certificate_chain: + type: "string" + description: "Base64 string with PEM-encoded certificates chain" + private_key: + type: "string" + description: "Base64 string with PEM-encoded private key" + # Below goes validation fields + valid_cert: + type: "boolean" + example: "true" + description: "valid_cert is true if the specified certificates chain is a valid chain of X509 certificates" + valid_chain: + type: "boolean" + example: "true" + description: "valid_chain is true if the specified certificates chain is verified and issued by a known CA" + subject: + type: "string" + example: "CN=example.org" + description: "subject is the subject of the first certificate in the chain" + issuer: + type: "string" + example: "CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US" + description: "issuer is the issuer of the first certificate in the chain" + not_before: + type: "string" + example: "2019-01-31T10:47:32Z" + description: "not_before is the NotBefore field of the first certificate in the chain" + not_after: + type: "string" + example: "2019-05-01T10:47:32Z" + description: "not_after is the NotAfter field of the first certificate in the chain" + dns_names: + type: "array" + items: + type: "string" + description: "dns_names is the value of SubjectAltNames field of the first certificate in the chain" + example: + - "*.example.org" + valid_key: + type: "boolean" + example: "true" + description: "valid_key is true if the key is a valid private key" + key_type: + type: "string" + example: "RSA" + description: "key_type is either RSA or ECDSA" + warning_validation: + type: "string" + example: "You have specified an empty certificate" + description: "warning_validation is a validation warning message with the issue description" \ No newline at end of file