Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkg/security: relax requirement to follow CRDB URI SAN cert scheme #87302

Merged
merged 1 commit into from
Sep 7, 2022

Conversation

abarganier
Copy link
Contributor

@abarganier abarganier commented Sep 1, 2022

Recently, a customer upgraded to v22.1.6, a recent patch release which
contains the new tenant-scoped client certificates and asssociated
authorization logic updates.

The new authz logic required that the SAN URIs included in the client
certificate followed the URI SAN scheme:
crdb://tenant/<tenant_id>/user/<tenant_username>

However, for customers that use URI SANs that do not follow this
convention or do not have the flexibility to alter the URI SAN,
this was preventing them from using their existing certificates.
This would generate an error when attempting to connect to a
SQL shell.

One example URI SAN is as follows:
mycompany:sv:rootclient:dev:usw1

This is a certificate that worked with the legacy behavior, but
is rejected by the new authz logic.

We should update the authz logic to be less strict about the URI SAN
following our own scheme. If we are unable to parse the URI SAN then
we should fallback to using the globally scoped client certificate
instead, enabling backwards compatibility.

This patch does just that, logging an error in the case where we are
unable to parse the URI SAN and instead falling back to the legacy
behavior, producing a global user scope for the certificate.

Release note: none

Release justification: low risk, necessary fix to enable customers
using custom URI SAN schemes to continue using their existing certificates
on newer CRDB versions.

Addresses #87235

@cockroach-teamcity
Copy link
Member

This change is Reviewable

@abarganier abarganier force-pushed the tenant-cert-urisan-parse branch from d28ea85 to 815e6e0 Compare September 1, 2022 18:47
Copy link
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior without this PR is indeed a problem (#87235), but the change in this PR does not really improve the situation.

The better approach would be to understand that the SAN field can contain multiple entries. It's possible for a certificate to contain both a mycompany: URI and also a crdb: URI.

The logic in crdb should detect (and parse) crdb: URIs and ignore the non-crdb: URIs.

If it parses a crdb: URI, and an error gets encountered (e.g. because the crdb: URI has invalid syntax.) that error should not be ignored.

Reviewed 1 of 4 files at r1.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @abarganier, @catj-cockroach, @jeffswenson, @jonstjohn, and @rimadeodhar)


pkg/security/auth.go line 118 at r1 (raw file):

	peerCert := tlsState.PeerCertificates[0]
	for _, uri := range peerCert.URIs {
		tenantID, user, err := ParseTenantURISAN(uri.String())

Only call this function if the URI scheme is crdb.
(If the scheme is not crdb, ignore the URI and try the next URI instead).


pkg/security/auth.go line 120 at r1 (raw file):

		tenantID, user, err := ParseTenantURISAN(uri.String())
		if err != nil {
			log.Errorf(ctx, "unable to parse tenant URI SAN, defaulting to global certificate: %v", err)

Don't ignore the error if we decided to parse the URI.


pkg/security/auth.go line 133 at r1 (raw file):

		userScopes = append(userScopes, scope)
	}
	if len(userScopes) == 0 || len(userScopes) != len(peerCert.URIs) {

I don't understand this length comparison. What is it trying to check?

Copy link
Contributor

@kpatron-cockroachlabs kpatron-cockroachlabs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @knz that we should not immediately fall back to global certs if any one SAN field doesn't match our format, given the likelihood that customer certs will include extra non-CRDB related SANs (or crdb: SANs for other clusters, etc).

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @abarganier, @catj-cockroach, @jeffswenson, @jonstjohn, and @rimadeodhar)

Copy link
Contributor Author

@abarganier abarganier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback all. I agree with the proposed changes, I'll get a patch up tomorrow.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @abarganier, @catj-cockroach, @jeffswenson, @jonstjohn, and @rimadeodhar)

@abarganier abarganier force-pushed the tenant-cert-urisan-parse branch from 815e6e0 to 13009be Compare September 7, 2022 15:56
Copy link
Contributor Author

@abarganier abarganier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the authz logic as requested. We now look through all the URI SANs included in the certificate for one with the expected crdb:// prefix. If we find a prefix match, we attempt to parse the URI SAN as before, in which case we take a strict approach where we throw an error if the format isn't the expected crdb://tenant/<tenant_id>/user/<tenant_username>.

In the case where none of the provided URI SANs have the expected crdb:// prefix, only then do we fallback to the global cert.

I've added some tests to cover these scenarios. Thanks again for everyone's input/help.

PTAL.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @catj-cockroach, @jeffswenson, @jonstjohn, @knz, and @rimadeodhar)


pkg/security/auth.go line 118 at r1 (raw file):

Previously, knz (Raphael 'kena' Poss) wrote…

Only call this function if the URI scheme is crdb.
(If the scheme is not crdb, ignore the URI and try the next URI instead).

Done.


pkg/security/auth.go line 120 at r1 (raw file):

Previously, knz (Raphael 'kena' Poss) wrote…

Don't ignore the error if we decided to parse the URI.

Done. Added some tests around these scenarios.


pkg/security/auth.go line 133 at r1 (raw file):

Previously, knz (Raphael 'kena' Poss) wrote…

I don't understand this length comparison. What is it trying to check?

This was a misunderstanding on my part - removed. As before, we should only fall back to the global cert if none of the URIs were of the expected CRDB format.

Copy link
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm: nice

Reviewed 5 of 5 files at r2, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @abarganier, @catj-cockroach, @jeffswenson, @jonstjohn, @kpatron-cockroachlabs, and @rimadeodhar)


pkg/security/auth.go line 117 at r2 (raw file):

	peerCert := tlsState.PeerCertificates[0]
	for _, uri := range peerCert.URIs {
		if URISANHasCRDBPrefix(uri.String()) {

best store uri.String() in an intermediate var.

Recently, a customer upgraded to v22.1.6, a recent patch release which
contains the new tenant-scoped client certificates and asssociated
authorization logic updates.

The new authz logic *required* that the SAN URIs included in the client
certificate followed the URI SAN scheme:
`crdb://tenant/<tenant_id>/user/<tenant_username>`

However, for customers that use URI SANs that do not follow this
convention or do not have the flexibility to alter the URI SAN,
this was preventing them from using their existing certificates.
This would generate an error when attempting to connect to a
SQL shell.

One example URI SAN is as follows:
`mycompany:sv:rootclient:dev:usw1`

This is a certificate that worked with the legacy behavior, but
is rejected by the new authz logic.

We should update the authz logic to be less strict about the URI SAN
following the CRDB format. The new logic is as follows:

If any one of the URI SANs has the expected `crdb://` prefix, we will
still attempt to parse, and error-out if parsing fails.

If none of the URI SANs have the expected `crdb://` prefix, then we
can fallback to the legacy behavior and interpret as a global scoped
certificate.

Release note: none

Release justification: low risk, necessary fix to enable customers
using custom URI SAN schemes to continue using their existing certificates
on newer CRDB versions.
@abarganier abarganier force-pushed the tenant-cert-urisan-parse branch from ab64458 to a2fe4c8 Compare September 7, 2022 18:57
Copy link
Contributor Author

@abarganier abarganier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TFTR!

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @catj-cockroach, @jeffswenson, @jonstjohn, @knz, @kpatron-cockroachlabs, and @rimadeodhar)


pkg/security/auth.go line 117 at r2 (raw file):

Previously, knz (Raphael 'kena' Poss) wrote…

best store uri.String() in an intermediate var.

Done.

@abarganier
Copy link
Contributor Author

bors r=knz

@craig
Copy link
Contributor

craig bot commented Sep 7, 2022

Build failed (retrying...):

@craig craig bot merged commit e39111b into cockroachdb:master Sep 7, 2022
@craig
Copy link
Contributor

craig bot commented Sep 7, 2022

Build succeeded:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants