diff --git a/docs/content/middlewares/http/passtlsclientcert.md b/docs/content/middlewares/http/passtlsclientcert.md
index fa65553f6..89a802a13 100644
--- a/docs/content/middlewares/http/passtlsclientcert.md
+++ b/docs/content/middlewares/http/passtlsclientcert.md
@@ -76,6 +76,7 @@ http:
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.domaincomponent=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.locality=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organization=true"
+ - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organizationalunit=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.province=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.serialnumber=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.issuer.commonname=true"
@@ -104,6 +105,7 @@ http:
province: true
locality: true
organization: true
+ organizationalUnit: true
commonName: true
serialNumber: true
domainComponent: true
@@ -127,6 +129,7 @@ http:
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.domaincomponent=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.locality=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organization=true"
+ - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organizationalunit=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.province=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.serialnumber=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.issuer.commonname=true"
@@ -148,6 +151,7 @@ http:
"traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.domaincomponent": "true",
"traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.locality": "true",
"traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organization": "true",
+ "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organizationalunit": "true",
"traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.province": "true",
"traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.serialnumber": "true",
"traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.issuer.commonname": "true",
@@ -171,6 +175,7 @@ http:
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.domaincomponent=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.locality=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organization=true"
+ - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.organizationalunit=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.province=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.subject.serialnumber=true"
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.issuer.commonname=true"
@@ -197,6 +202,7 @@ http:
province: true
locality: true
organization: true
+ organizationalUnit: true
commonName: true
serialNumber: true
domainComponent: true
@@ -223,6 +229,7 @@ http:
province = true
locality = true
organization = true
+ organizationalUnit = true
commonName = true
serialNumber = true
domainComponent = true
@@ -247,7 +254,7 @@ PassTLSClientCert can add two headers to the request:
!!! info
- * The headers are filled with escaped string so it can be safely placed inside a URL query.
+ * Each header value is a string that has been escaped in order to be a valid URL query.
* These options only work accordingly to the [MutualTLS configuration](../../https/tls.md#client-authentication-mtls).
That is to say, only the certificates that match the `clientAuth.clientAuthType` policy are passed.
@@ -412,15 +419,18 @@ In the example, it is the part between `-----BEGIN CERTIFICATE-----` and `-----E
!!! warning "`X-Forwarded-Tls-Client-Cert` value could exceed the web server header size limit"
The header size limit of web servers is commonly between 4kb and 8kb.
- You could change the server configuration to allow bigger header or use the `info` option with the needed field(s).
+ If that turns out to be a problem, and if reconfiguring the server to allow larger headers is not an option,
+ one can alleviate the problem by selecting only the interesting parts of the cert,
+ through the use of the `info` options described below. (And by setting `pem` to false).
### `info`
The `info` option selects the specific client certificate details you want to add to the `X-Forwarded-Tls-Client-Cert-Info` header.
The value of the header is an escaped concatenation of all the selected certificate details.
+But in the following, unless specified otherwise, all the header values examples are shown unescaped, for readability.
-The following example shows an unescaped result that uses all the available fields:
+The following example shows such a concatenation, when all the available fields are selected:
```text
Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.example.com";Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2";NB="1544094616";NA="1607166616";SAN="*.example.org,*.example.net,*.example.com,test@example.org,test@example.net,10.0.1.0,10.0.1.2"
@@ -441,7 +451,7 @@ The data is taken from the following certificate part:
Not After : Dec 5 11:10:16 2020 GMT
```
-The escaped `notAfter` info part is formatted as below:
+And it is formatted as follows in the header:
```text
NA="1607166616"
@@ -458,7 +468,7 @@ Validity
Not Before: Dec 6 11:10:16 2018 GMT
```
-The escaped `notBefore` info part is formatted as below:
+And it is formatted as follows in the header:
```text
NB="1544094616"
@@ -475,7 +485,7 @@ The data is taken from the following certificate part:
DNS:*.example.org, DNS:*.example.net, DNS:*.example.com, IP Address:10.0.1.0, IP Address:10.0.1.2, email:test@example.org, email:test@example.net
```
-The escape SANs info part is formatted as below:
+And it is formatted as follows in the header:
```text
SAN="*.example.org,*.example.net,*.example.com,test@example.org,test@example.net,10.0.1.0,10.0.1.2"
@@ -501,7 +511,7 @@ Set the `info.subject.country` option to `true` to add the `country` information
The data is taken from the subject part with the `C` key.
-The escape country info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
C=FR,C=US
@@ -513,7 +523,7 @@ Set the `info.subject.province` option to `true` to add the `province` informati
The data is taken from the subject part with the `ST` key.
-The escape province info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
ST=Cheese org state,ST=Cheese com state
@@ -525,7 +535,7 @@ Set the `info.subject.locality` option to `true` to add the `locality` informati
The data is taken from the subject part with the `L` key.
-The escape locality info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
L=TOULOUSE,L=LYON
@@ -537,19 +547,31 @@ Set the `info.subject.organization` option to `true` to add the `organization` i
The data is taken from the subject part with the `O` key.
-The escape organization info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
O=Cheese,O=Cheese 2
```
+##### `info.subject.organizationalUnit`
+
+Set the `info.subject.organizationalUnit` option to `true` to add the `organizationalUnit` information into the subject.
+
+The data is taken from the subject part with the `OU` key.
+
+And it is formatted as follows in the header:
+
+```text
+OU=Cheese Section,OU=Cheese Section 2
+```
+
##### `info.subject.commonName`
Set the `info.subject.commonName` option to `true` to add the `commonName` information into the subject.
The data is taken from the subject part with the `CN` key.
-The escape common name info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
CN=*.example.com
@@ -561,7 +583,7 @@ Set the `info.subject.serialNumber` option to `true` to add the `serialNumber` i
The data is taken from the subject part with the `SN` key.
-The escape serial number info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
SN=1234567890
@@ -573,7 +595,7 @@ Set the `info.subject.domainComponent` option to `true` to add the `domainCompon
The data is taken from the subject part with the `DC` key.
-The escape domain component info in the subject part is formatted as below:
+And it is formatted as follows in the header:
```text
DC=org,DC=cheese
@@ -595,7 +617,7 @@ Set the `info.issuer.country` option to `true` to add the `country` information
The data is taken from the issuer part with the `C` key.
-The escape country info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
C=FR,C=US
@@ -607,7 +629,7 @@ Set the `info.issuer.province` option to `true` to add the `province` informatio
The data is taken from the issuer part with the `ST` key.
-The escape province info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
ST=Signing State,ST=Signing State 2
@@ -619,7 +641,7 @@ Set the `info.issuer.locality` option to `true` to add the `locality` informatio
The data is taken from the issuer part with the `L` key.
-The escape locality info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
L=TOULOUSE,L=LYON
@@ -631,7 +653,7 @@ Set the `info.issuer.organization` option to `true` to add the `organization` in
The data is taken from the issuer part with the `O` key.
-The escape organization info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
O=Cheese,O=Cheese 2
@@ -643,7 +665,7 @@ Set the `info.issuer.commonName` option to `true` to add the `commonName` inform
The data is taken from the issuer part with the `CN` key.
-The escape common name info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
CN=Simple Signing CA 2
@@ -655,7 +677,7 @@ Set the `info.issuer.serialNumber` option to `true` to add the `serialNumber` in
The data is taken from the issuer part with the `SN` key.
-The escape serial number info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
SN=1234567890
@@ -667,7 +689,7 @@ Set the `info.issuer.domainComponent` option to `true` to add the `domainCompone
The data is taken from the issuer part with the `DC` key.
-The escape domain component info in the issuer part is formatted as below:
+And it is formatted as follows in the header:
```text
DC=org,DC=cheese
diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml
index cd4f59243..82574efdd 100644
--- a/docs/content/reference/dynamic-configuration/docker-labels.yml
+++ b/docs/content/reference/dynamic-configuration/docker-labels.yml
@@ -90,6 +90,7 @@
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.domaincomponent=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.locality=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organization=true"
+- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organizationalunit=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.province=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.serialnumber=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.pem=true"
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index d531f81a1..a7ac27b77 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -217,6 +217,7 @@
province = true
locality = true
organization = true
+ organizationalUnit = true
commonName = true
serialNumber = true
domainComponent = true
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index 515dba07c..3a124cbfe 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -250,6 +250,7 @@ http:
province: true
locality: true
organization: true
+ organizationalUnit: true
commonName: true
serialNumber: true
domainComponent: true
diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md
index b4c81d76b..d06c339e0 100644
--- a/docs/content/reference/dynamic-configuration/kv-ref.md
+++ b/docs/content/reference/dynamic-configuration/kv-ref.md
@@ -106,6 +106,7 @@
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/locality` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organization` | `true` |
+| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organizationalUnit` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/province` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/pem` | `true` |
diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json
index 65f2c74c4..93aa9c6d1 100644
--- a/docs/content/reference/dynamic-configuration/marathon-labels.json
+++ b/docs/content/reference/dynamic-configuration/marathon-labels.json
@@ -90,6 +90,7 @@
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.domaincomponent": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.locality": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organization": "true",
+"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organizationalunit": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.province": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.serialnumber": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.pem": "true",
diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml
index 4ce827116..c85effec0 100644
--- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml
+++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml
@@ -398,8 +398,9 @@ spec:
info configuration.
properties:
issuer:
- description: TLSCLientCertificateDNInfo holds the client TLS
- certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739
+ description: TLSCLientCertificateIssuerDNInfo holds the client
+ TLS certificate distinguished name info configuration. cf
+ https://tools.ietf.org/html/rfc3739
properties:
commonName:
type: boolean
@@ -425,8 +426,9 @@ spec:
serialNumber:
type: boolean
subject:
- description: TLSCLientCertificateDNInfo holds the client TLS
- certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739
+ description: TLSCLientCertificateSubjectDNInfo holds the client
+ TLS certificate distinguished name info configuration. cf
+ https://tools.ietf.org/html/rfc3739
properties:
commonName:
type: boolean
@@ -438,6 +440,8 @@ spec:
type: boolean
organization:
type: boolean
+ organizationalUnit:
+ type: boolean
province:
type: boolean
serialNumber:
diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml
index 84ca0e756..543bb577a 100644
--- a/integration/fixtures/k8s/01-traefik-crd.yml
+++ b/integration/fixtures/k8s/01-traefik-crd.yml
@@ -840,8 +840,9 @@ spec:
info configuration.
properties:
issuer:
- description: TLSCLientCertificateDNInfo holds the client TLS
- certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739
+ description: TLSCLientCertificateIssuerDNInfo holds the client
+ TLS certificate distinguished name info configuration. cf
+ https://tools.ietf.org/html/rfc3739
properties:
commonName:
type: boolean
@@ -867,8 +868,9 @@ spec:
serialNumber:
type: boolean
subject:
- description: TLSCLientCertificateDNInfo holds the client TLS
- certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739
+ description: TLSCLientCertificateSubjectDNInfo holds the client
+ TLS certificate distinguished name info configuration. cf
+ https://tools.ietf.org/html/rfc3739
properties:
commonName:
type: boolean
@@ -880,6 +882,8 @@ spec:
type: boolean
organization:
type: boolean
+ organizationalUnit:
+ type: boolean
province:
type: boolean
serialNumber:
diff --git a/pkg/anonymize/anonymize_config_test.go b/pkg/anonymize/anonymize_config_test.go
index 8a1133fca..831bdea65 100644
--- a/pkg/anonymize/anonymize_config_test.go
+++ b/pkg/anonymize/anonymize_config_test.go
@@ -315,16 +315,17 @@ func TestDo_dynamicConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
Sans: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- Country: true,
- Province: true,
- Locality: true,
- Organization: true,
- CommonName: true,
- SerialNumber: true,
- DomainComponent: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Country: true,
+ Province: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ CommonName: true,
+ SerialNumber: true,
+ DomainComponent: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
diff --git a/pkg/anonymize/testdata/anonymized-dynamic-config.json b/pkg/anonymize/testdata/anonymized-dynamic-config.json
index 917fca5d7..c71b0cb0a 100644
--- a/pkg/anonymize/testdata/anonymized-dynamic-config.json
+++ b/pkg/anonymize/testdata/anonymized-dynamic-config.json
@@ -288,6 +288,7 @@
"province": true,
"locality": true,
"organization": true,
+ "organizationalUnit": true,
"commonName": true,
"serialNumber": true,
"domainComponent": true
diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml
index 6bacc6f80..1c2defb0a 100644
--- a/pkg/config/dynamic/fixtures/sample.toml
+++ b/pkg/config/dynamic/fixtures/sample.toml
@@ -330,6 +330,7 @@
province = true
locality = true
organization = true
+ organizationalUnit = true
commonName = true
serialNumber = true
domainComponent = true
diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go
index 0b3452f0a..136901af7 100644
--- a/pkg/config/dynamic/middlewares.go
+++ b/pkg/config/dynamic/middlewares.go
@@ -399,19 +399,19 @@ type StripPrefixRegex struct {
// TLSClientCertificateInfo holds the client TLS certificate info configuration.
type TLSClientCertificateInfo struct {
- NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty" export:"true"`
- NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty" export:"true"`
- Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty" export:"true"`
- Subject *TLSCLientCertificateDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"`
- Issuer *TLSCLientCertificateDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"`
- SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"`
+ NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty" export:"true"`
+ NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty" export:"true"`
+ Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty" export:"true"`
+ Subject *TLSCLientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"`
+ Issuer *TLSCLientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"`
+ SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true
-// TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration.
+// TLSCLientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration.
// cf https://tools.ietf.org/html/rfc3739
-type TLSCLientCertificateDNInfo struct {
+type TLSCLientCertificateIssuerDNInfo struct {
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
@@ -423,6 +423,21 @@ type TLSCLientCertificateDNInfo struct {
// +k8s:deepcopy-gen=true
+// TLSCLientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration.
+// cf https://tools.ietf.org/html/rfc3739
+type TLSCLientCertificateSubjectDNInfo struct {
+ Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
+ Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
+ Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
+ Organization bool `json:"organization,omitempty" toml:"organization,omitempty" yaml:"organization,omitempty" export:"true"`
+ OrganizationalUnit bool `json:"organizationalUnit,omitempty" toml:"organizationalUnit,omitempty" yaml:"organizationalUnit,omitempty" export:"true"`
+ CommonName bool `json:"commonName,omitempty" toml:"commonName,omitempty" yaml:"commonName,omitempty" export:"true"`
+ SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"`
+ DomainComponent bool `json:"domainComponent,omitempty" toml:"domainComponent,omitempty" yaml:"domainComponent,omitempty" export:"true"`
+}
+
+// +k8s:deepcopy-gen=true
+
// Users holds a list of users.
type Users []string
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index 742e59c65..44293e7ad 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -1535,17 +1535,33 @@ func (in *TCPWeightedRoundRobin) DeepCopy() *TCPWeightedRoundRobin {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *TLSCLientCertificateDNInfo) DeepCopyInto(out *TLSCLientCertificateDNInfo) {
+func (in *TLSCLientCertificateIssuerDNInfo) DeepCopyInto(out *TLSCLientCertificateIssuerDNInfo) {
*out = *in
return
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateDNInfo.
-func (in *TLSCLientCertificateDNInfo) DeepCopy() *TLSCLientCertificateDNInfo {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateIssuerDNInfo.
+func (in *TLSCLientCertificateIssuerDNInfo) DeepCopy() *TLSCLientCertificateIssuerDNInfo {
if in == nil {
return nil
}
- out := new(TLSCLientCertificateDNInfo)
+ out := new(TLSCLientCertificateIssuerDNInfo)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TLSCLientCertificateSubjectDNInfo) DeepCopyInto(out *TLSCLientCertificateSubjectDNInfo) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateSubjectDNInfo.
+func (in *TLSCLientCertificateSubjectDNInfo) DeepCopy() *TLSCLientCertificateSubjectDNInfo {
+ if in == nil {
+ return nil
+ }
+ out := new(TLSCLientCertificateSubjectDNInfo)
in.DeepCopyInto(out)
return out
}
@@ -1555,12 +1571,12 @@ func (in *TLSClientCertificateInfo) DeepCopyInto(out *TLSClientCertificateInfo)
*out = *in
if in.Subject != nil {
in, out := &in.Subject, &out.Subject
- *out = new(TLSCLientCertificateDNInfo)
+ *out = new(TLSCLientCertificateSubjectDNInfo)
**out = **in
}
if in.Issuer != nil {
in, out := &in.Issuer, &out.Issuer
- *out = new(TLSCLientCertificateDNInfo)
+ *out = new(TLSCLientCertificateIssuerDNInfo)
**out = **in
}
return
diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go
index 3abbd57ea..eac688246 100644
--- a/pkg/config/label/label_test.go
+++ b/pkg/config/label/label_test.go
@@ -95,6 +95,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.domaincomponent": "true",
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.locality": "true",
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.organization": "true",
+ "traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.organizationalunit": "true",
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.province": "true",
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.serialnumber": "true",
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.commonname": "true",
@@ -366,16 +367,17 @@ func TestDecodeConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
SerialNumber: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- Country: true,
- Province: true,
- Locality: true,
- Organization: true,
- CommonName: true,
- SerialNumber: true,
- DomainComponent: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Country: true,
+ Province: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ CommonName: true,
+ SerialNumber: true,
+ DomainComponent: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@@ -844,16 +846,17 @@ func TestEncodeConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
SerialNumber: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- Country: true,
- Province: true,
- Locality: true,
- Organization: true,
- CommonName: true,
- SerialNumber: true,
- DomainComponent: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Country: true,
+ Province: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ CommonName: true,
+ SerialNumber: true,
+ DomainComponent: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@@ -1234,6 +1237,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Province": "true",
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Locality": "true",
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Organization": "true",
+ "traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.OrganizationalUnit": "true",
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.CommonName": "true",
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.SerialNumber": "true",
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.DomainComponent": "true",
diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go
index 682836d56..3089f02d5 100644
--- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go
+++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go
@@ -35,8 +35,10 @@ var attributeTypeNames = map[string]string{
"0.9.2342.19200300.100.1.25": "DC", // Domain component OID - RFC 2247
}
-// DistinguishedNameOptions is a struct for specifying the configuration for the distinguished name info.
-type DistinguishedNameOptions struct {
+// IssuerDistinguishedNameOptions is a struct for specifying the configuration
+// for the distinguished name info of the issuer. This information is defined in
+// RFC3739, section 3.1.1.
+type IssuerDistinguishedNameOptions struct {
CommonName bool
CountryName bool
DomainComponent bool
@@ -46,12 +48,12 @@ type DistinguishedNameOptions struct {
StateOrProvinceName bool
}
-func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *DistinguishedNameOptions {
+func newIssuerDistinguishedNameOptions(info *dynamic.TLSCLientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions {
if info == nil {
return nil
}
- return &DistinguishedNameOptions{
+ return &IssuerDistinguishedNameOptions{
CommonName: info.CommonName,
CountryName: info.Country,
DomainComponent: info.DomainComponent,
@@ -62,13 +64,44 @@ func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *Dist
}
}
+// SubjectDistinguishedNameOptions is a struct for specifying the configuration
+// for the distinguished name info of the subject. This information is defined
+// in RFC3739, section 3.1.2.
+type SubjectDistinguishedNameOptions struct {
+ CommonName bool
+ CountryName bool
+ DomainComponent bool
+ LocalityName bool
+ OrganizationName bool
+ OrganizationalUnitName bool
+ SerialNumber bool
+ StateOrProvinceName bool
+}
+
+func newSubjectDistinguishedNameOptions(info *dynamic.TLSCLientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions {
+ if info == nil {
+ return nil
+ }
+
+ return &SubjectDistinguishedNameOptions{
+ CommonName: info.CommonName,
+ CountryName: info.Country,
+ DomainComponent: info.DomainComponent,
+ LocalityName: info.Locality,
+ OrganizationName: info.Organization,
+ OrganizationalUnitName: info.OrganizationalUnit,
+ SerialNumber: info.SerialNumber,
+ StateOrProvinceName: info.Province,
+ }
+}
+
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
type tlsClientCertificateInfo struct {
notAfter bool
notBefore bool
sans bool
- subject *DistinguishedNameOptions
- issuer *DistinguishedNameOptions
+ subject *SubjectDistinguishedNameOptions
+ issuer *IssuerDistinguishedNameOptions
serialNumber bool
}
@@ -78,10 +111,10 @@ func newTLSClientCertificateInfo(info *dynamic.TLSClientCertificateInfo) *tlsCli
}
return &tlsClientCertificateInfo{
- issuer: newDistinguishedNameOptions(info.Issuer),
+ issuer: newIssuerDistinguishedNameOptions(info.Issuer),
notAfter: info.NotAfter,
notBefore: info.NotBefore,
- subject: newDistinguishedNameOptions(info.Subject),
+ subject: newSubjectDistinguishedNameOptions(info.Subject),
serialNumber: info.SerialNumber,
sans: info.Sans,
}
@@ -147,12 +180,12 @@ func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certi
var values []string
if p.info != nil {
- subject := getDNInfo(ctx, p.info.subject, &peerCert.Subject)
+ subject := getSubjectDNInfo(ctx, p.info.subject, &peerCert.Subject)
if subject != "" {
values = append(values, fmt.Sprintf(`Subject="%s"`, strings.TrimSuffix(subject, subFieldSeparator)))
}
- issuer := getDNInfo(ctx, p.info.issuer, &peerCert.Issuer)
+ issuer := getIssuerDNInfo(ctx, p.info.issuer, &peerCert.Issuer)
if issuer != "" {
values = append(values, fmt.Sprintf(`Issuer="%s"`, strings.TrimSuffix(issuer, subFieldSeparator)))
}
@@ -187,7 +220,7 @@ func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certi
return strings.Join(headerValues, certSeparator)
}
-func getDNInfo(ctx context.Context, options *DistinguishedNameOptions, cs *pkix.Name) string {
+func getIssuerDNInfo(ctx context.Context, options *IssuerDistinguishedNameOptions, cs *pkix.Name) string {
if options == nil {
return ""
}
@@ -229,6 +262,52 @@ func getDNInfo(ctx context.Context, options *DistinguishedNameOptions, cs *pkix.
return content.String()
}
+func getSubjectDNInfo(ctx context.Context, options *SubjectDistinguishedNameOptions, cs *pkix.Name) string {
+ if options == nil {
+ return ""
+ }
+
+ content := &strings.Builder{}
+
+ // Manage non standard attributes
+ for _, name := range cs.Names {
+ // Domain Component - RFC 2247
+ if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
+ content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
+ }
+ }
+
+ if options.CountryName {
+ writeParts(ctx, content, cs.Country, "C")
+ }
+
+ if options.StateOrProvinceName {
+ writeParts(ctx, content, cs.Province, "ST")
+ }
+
+ if options.LocalityName {
+ writeParts(ctx, content, cs.Locality, "L")
+ }
+
+ if options.OrganizationName {
+ writeParts(ctx, content, cs.Organization, "O")
+ }
+
+ if options.OrganizationalUnitName {
+ writeParts(ctx, content, cs.OrganizationalUnit, "OU")
+ }
+
+ if options.SerialNumber {
+ writePart(ctx, content, cs.SerialNumber, "SN")
+ }
+
+ if options.CommonName {
+ writePart(ctx, content, cs.CommonName, "CN")
+ }
+
+ return content.String()
+}
+
func writeParts(ctx context.Context, content io.StringWriter, entries []string, prefix string) {
for _, entry := range entries {
writePart(ctx, content, entry, prefix)
diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go
index 934dbff1a..b4a56d208 100644
--- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go
+++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go
@@ -310,22 +310,22 @@ func TestPassTLSClientCert_PEM(t *testing.T) {
}
for _, test := range testCases {
- tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
- require.NoError(t, err)
-
- res := httptest.NewRecorder()
- req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
-
- if test.certContents != nil && len(test.certContents) > 0 {
- req.TLS = buildTLSWith(test.certContents)
- }
-
- tlsClientHeaders.ServeHTTP(res, req)
-
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
+ tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
+ require.NoError(t, err)
+
+ res := httptest.NewRecorder()
+ req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
+
+ if test.certContents != nil && len(test.certContents) > 0 {
+ req.TLS = buildTLSWith(test.certContents)
+ }
+
+ tlsClientHeaders.ServeHTTP(res, req)
+
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
@@ -351,7 +351,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
}, fieldSeparator)
completeCertAllInfo := strings.Join([]string{
- `Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com"`,
+ `Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,OU=Simple Signing Section,OU=Simple Signing Section 2,CN=*.cheese.com"`,
`Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`,
`SerialNumber="1"`,
`NB="1544094616"`,
@@ -376,13 +376,14 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
desc: "No TLS, with subject info",
config: dynamic.PassTLSClientCert{
Info: &dynamic.TLSClientCertificateInfo{
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- CommonName: true,
- Organization: true,
- Locality: true,
- Province: true,
- Country: true,
- SerialNumber: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ CommonName: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ Locality: true,
+ Province: true,
+ Country: true,
+ SerialNumber: true,
},
},
},
@@ -392,7 +393,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
config: dynamic.PassTLSClientCert{
PEM: false,
Info: &dynamic.TLSClientCertificateInfo{
- Subject: &dynamic.TLSCLientCertificateDNInfo{},
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{},
},
},
},
@@ -405,16 +406,17 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- CommonName: true,
- Country: true,
- DomainComponent: true,
- Locality: true,
- Organization: true,
- Province: true,
- SerialNumber: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ CommonName: true,
+ Country: true,
+ DomainComponent: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ Province: true,
+ SerialNumber: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
CommonName: true,
Country: true,
DomainComponent: true,
@@ -434,10 +436,30 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
Sans: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Organization: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
+ Country: true,
+ },
+ },
+ },
+ expectedHeader: `Subject="O=Cheese";Issuer="C=FR,C=US";NA="1632568236"`,
+ },
+ {
+ desc: "TLS with simple certificate, requesting non-existent info",
+ certContents: []string{minimalCheeseCrt},
+ config: dynamic.PassTLSClientCert{
+ Info: &dynamic.TLSClientCertificateInfo{
+ NotAfter: true,
+ Sans: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Organization: true,
+ // OrganizationalUnit is not set on this example certificate,
+ // so even though it's requested, it will be absent.
+ OrganizationalUnit: true,
+ },
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
},
},
@@ -453,16 +475,17 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- Country: true,
- Province: true,
- Locality: true,
- Organization: true,
- CommonName: true,
- SerialNumber: true,
- DomainComponent: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Country: true,
+ Province: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ CommonName: true,
+ SerialNumber: true,
+ DomainComponent: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@@ -484,16 +507,17 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- Country: true,
- Province: true,
- Locality: true,
- Organization: true,
- CommonName: true,
- SerialNumber: true,
- DomainComponent: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Country: true,
+ Province: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ CommonName: true,
+ SerialNumber: true,
+ DomainComponent: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@@ -509,22 +533,22 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
}
for _, test := range testCases {
- tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
- require.NoError(t, err)
-
- res := httptest.NewRecorder()
- req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
-
- if test.certContents != nil && len(test.certContents) > 0 {
- req.TLS = buildTLSWith(test.certContents)
- }
-
- tlsClientHeaders.ServeHTTP(res, req)
-
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
+ tlsClientHeaders, err := New(context.Background(), next, test.config, "foo")
+ require.NoError(t, err)
+
+ res := httptest.NewRecorder()
+ req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
+
+ if test.certContents != nil && len(test.certContents) > 0 {
+ req.TLS = buildTLSWith(test.certContents)
+ }
+
+ tlsClientHeaders.ServeHTTP(res, req)
+
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
@@ -621,12 +645,12 @@ func Test_getSANs(t *testing.T) {
}
for _, test := range testCases {
- sans := getSANs(test.cert)
-
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
+ sans := getSANs(test.cert)
+
if len(test.expected) > 0 {
for i, expected := range test.expected {
assert.Equal(t, expected, sans[i])
diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go
index 38896001a..a04093f18 100644
--- a/pkg/provider/kv/kv_test.go
+++ b/pkg/provider/kv/kv_test.go
@@ -156,6 +156,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/province": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/locality": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/organization": "true",
+ "traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/organizationalunit": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/commonName": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/serialNumber": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/domainComponent": "true",
@@ -478,16 +479,17 @@ func Test_buildConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
Sans: true,
- Subject: &dynamic.TLSCLientCertificateDNInfo{
- Country: true,
- Province: true,
- Locality: true,
- Organization: true,
- CommonName: true,
- SerialNumber: true,
- DomainComponent: true,
+ Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
+ Country: true,
+ Province: true,
+ Locality: true,
+ Organization: true,
+ OrganizationalUnit: true,
+ CommonName: true,
+ SerialNumber: true,
+ DomainComponent: true,
},
- Issuer: &dynamic.TLSCLientCertificateDNInfo{
+ Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue
index 17de1d43e..6bf637bcc 100644
--- a/webui/src/components/_commons/PanelMiddlewares.vue
+++ b/webui/src/components/_commons/PanelMiddlewares.vue
@@ -866,17 +866,21 @@
-
Common Name
-
+
Organizational Unit
+
-
Serial Number
-
+
Common Name
+