diff --git a/db/pf-schema-X.Y.sql b/db/pf-schema-X.Y.sql index 83e8cb019c38..ee693b90dd9c 100644 --- a/db/pf-schema-X.Y.sql +++ b/db/pf-schema-X.Y.sql @@ -1619,6 +1619,33 @@ CREATE FUNCTION ROUND_TO_MONTH (d DATETIME) RETURNS DATETIME DETERMINISTIC RETURN DATE_ADD(DATE(d),interval -DAY(d)+1 DAY); + +-- +-- Create table node_tls +-- + +CREATE TABLE node_tls ( + `mac` varchar(17) NOT NULL PRIMARY KEY, + `TLSCertSerial` varchar(255) default NULL, + `TLSCertExpiration` varchar(255) default NULL, + `TLSCertValidSince` varchar(255) default NULL, + `TLSCertSubject` varchar(255) default NULL, + `TLSCertIssuer` varchar(255) default NULL, + `TLSCertCommonName` varchar(255) default NULL, + `TLSCertSubjectAltNameEmail` varchar(255) default NULL, + `TLSClientCertSerial` varchar(255) default NULL, + `TLSClientCertExpiration` varchar(255) default NULL, + `TLSClientCertValidSince` varchar(255) default NULL, + `TLSClientCertSubject` varchar(255) default NULL, + `TLSClientCertIssuer` varchar(255) default NULL, + `TLSClientCertCommonName` varchar(255) default NULL, + `TLSClientCertSubjectAltNameEmail` varchar(255) default NULL, + `TLSClientCertX509v3ExtendedKeyUsage` varchar(255) default NULL, + `TLSClientCertX509v3SubjectKeyIdentifier` varchar(255) default NULL, + `TLSClientCertX509v3AuthorityKeyIdentifier` varchar(255) default NULL, + `TLSClientCertX509v3ExtendedKeyUsageOID` varchar(255) default NULL +) ENGINE=InnoDB DEFAULT CHARACTER SET = 'utf8mb4' COLLATE = 'utf8mb4_general_ci'; + -- -- Updating to current version -- diff --git a/db/upgrade-X.X-X.Y.sql b/db/upgrade-X.X-X.Y.sql index 69582502bfaa..1f1f57f6bed9 100644 --- a/db/upgrade-X.X-X.Y.sql +++ b/db/upgrade-X.X-X.Y.sql @@ -70,6 +70,30 @@ ALTER TABLE radius_audit_log MODIFY created_at TIMESTAMP NOT NULL DEFAULT CURREN \! echo "Make psk unique"; ALTER TABLE person ADD CONSTRAINT UNIQUE person_psk (`psk`); +\! echo "Create table node_tls" + +CREATE TABLE IF NOT EXISTS node_tls ( + `mac` varchar(17) NOT NULL PRIMARY KEY, + `TLSCertSerial` varchar(255) default NULL, + `TLSCertExpiration` varchar(255) default NULL, + `TLSCertValidSince` varchar(255) default NULL, + `TLSCertSubject` varchar(255) default NULL, + `TLSCertIssuer` varchar(255) default NULL, + `TLSCertCommonName` varchar(255) default NULL, + `TLSCertSubjectAltNameEmail` varchar(255) default NULL, + `TLSClientCertSerial` varchar(255) default NULL, + `TLSClientCertExpiration` varchar(255) default NULL, + `TLSClientCertValidSince` varchar(255) default NULL, + `TLSClientCertSubject` varchar(255) default NULL, + `TLSClientCertIssuer` varchar(255) default NULL, + `TLSClientCertCommonName` varchar(255) default NULL, + `TLSClientCertSubjectAltNameEmail` varchar(255) default NULL, + `TLSClientCertX509v3ExtendedKeyUsage` varchar(255) default NULL, + `TLSClientCertX509v3SubjectKeyIdentifier` varchar(255) default NULL, + `TLSClientCertX509v3AuthorityKeyIdentifier` varchar(255) default NULL, + `TLSClientCertX509v3ExtendedKeyUsageOID` varchar(255) default NULL +) ENGINE=InnoDB DEFAULT CHARACTER SET = 'utf8mb4' COLLATE = 'utf8mb4_general_ci'; + \! echo "Incrementing PacketFence schema version..."; INSERT IGNORE INTO pf_version (id, version, created_at) VALUES (@VERSION_INT, CONCAT_WS('.', @MAJOR_VERSION, @MINOR_VERSION), NOW()); diff --git a/go/cron/flush_radius_audit_log_job.go b/go/cron/flush_radius_audit_log_job.go index f4828cacf075..df15d1355b7e 100644 --- a/go/cron/flush_radius_audit_log_job.go +++ b/go/cron/flush_radius_audit_log_job.go @@ -71,11 +71,11 @@ func (j *FlushRadiusAuditLogJob) Run() { log.LogError(ctx, fmt.Sprintf("%s error running: %s", j.Name(), err.Error())) continue } - jsonStr = string(s) } - + jsonStr = strings.Replace(jsonStr, "\\", "", -1) err := json.Unmarshal([]byte(jsonStr), &entry) + if err != nil { log.LogError(ctx, fmt.Sprintf("%s error running: %s", j.Name(), err.Error())) continue @@ -121,11 +121,33 @@ func (j *FlushRadiusAuditLogJob) flushLogs(entries [][]interface{}) error { return err } + // REPLACE in node_tls + sqlTLS, argsTLS, err := j.buildQueryTLS(entries) + if err != nil { + return err + } + + res, err = db.ExecContext( + ctx, + sqlTLS, + argsTLS..., + ) + + if err != nil { + return err + } + + _, err = res.RowsAffected() + if err != nil { + return err + } + log.LogInfo(ctx, fmt.Sprintf("Flushed %d radius_audit_log", rows)) return nil } const RADIUS_AUDIT_LOG_COLUMN_COUNT = 37 +const NODE_TLS_COLUMN_COUNT = 19 /* query = "INSERT INTO radius_audit_log \ @@ -232,6 +254,79 @@ func (j *FlushRadiusAuditLogJob) argsFromEntry(entry []interface{}) []interface{ return args } +func (j *FlushRadiusAuditLogJob) buildQueryTLS(entries [][]interface{}) (string, []interface{}, error) { + sql := ` +INSERT INTO node_tls + ( + mac, TLSCertSerial, TLSCertExpiration, TLSCertValidSince, + TLSCertSubject, TLSCertIssuer, TLSCertCommonName, + TLSCertSubjectAltNameEmail, TLSClientCertSerial, + TLSClientCertExpiration, TLSClientCertValidSince, + TLSClientCertSubject, TLSClientCertIssuer, + TLSClientCertCommonName, TLSClientCertSubjectAltNameEmail, + TLSClientCertX509v3ExtendedKeyUsage, + TLSClientCertX509v3SubjectKeyIdentifier, + TLSClientCertX509v3AuthorityKeyIdentifier, + TLSClientCertX509v3ExtendedKeyUsageOID + ) +VALUES ` + bind := "( ?" + strings.Repeat(",?", NODE_TLS_COLUMN_COUNT-1) + ")" + sql += bind + strings.Repeat(","+bind, len(entries)-1) + sql += ` + ON DUPLICATE KEY UPDATE TLSCertSerial = VALUES(TLSCertSerial), + TLSCertExpiration = VALUES(TLSCertExpiration), TLSCertValidSince = VALUES(TLSCertValidSince), + TLSCertSubject = VALUES(TLSCertSubject), TLSCertIssuer = VALUES(TLSCertIssuer), TLSCertCommonName = VALUES( TLSCertCommonName), + TLSCertSubjectAltNameEmail = VALUES(TLSCertSubjectAltNameEmail), TLSClientCertSerial = VALUES(TLSClientCertSerial), + TLSClientCertExpiration = VALUES(TLSClientCertExpiration), TLSClientCertValidSince = VALUES(TLSClientCertValidSince), + TLSClientCertSubject = VALUES(TLSClientCertSubject), TLSClientCertIssuer = VALUES(TLSClientCertIssuer), + TLSClientCertCommonName = VALUES(TLSClientCertCommonName), TLSClientCertSubjectAltNameEmail = VALUES(TLSClientCertSubjectAltNameEmail), + TLSClientCertX509v3ExtendedKeyUsage = VALUES(TLSClientCertX509v3ExtendedKeyUsage), + TLSClientCertX509v3SubjectKeyIdentifier = VALUES(TLSClientCertX509v3SubjectKeyIdentifier), + TLSClientCertX509v3AuthorityKeyIdentifier = VALUES(TLSClientCertX509v3AuthorityKeyIdentifier), + TLSClientCertX509v3ExtendedKeyUsageOID = VALUES(TLSClientCertX509v3ExtendedKeyUsageOID) + + ` + args := make([]interface{}, 0, NODE_TLS_COLUMN_COUNT) + for _, e := range entries { + if keyExists(e[1].(map[string]interface{}), "Calling-Station-Id") && keyExists(e[1].(map[string]interface{}), "TLS-Client-Cert-Common-Name") { + args = append(args, j.argsFromEntryForTLS(e)...) + } + } + return sql, args, nil +} + +func keyExists(myMap map[string]interface{}, key string) bool { + _, exists := myMap[key] + return exists +} + +func (j *FlushRadiusAuditLogJob) argsFromEntryForTLS(entry []interface{}) []interface{} { + args := make([]interface{}, NODE_TLS_COLUMN_COUNT) + var request map[string]interface{} + request = entry[1].(map[string]interface{}) + request = parseRequestArgs(request) + args[0] = formatRequestValue(request["Calling-Station-Id"], "") + args[1] = formatRequestValue(request["TLS-Cert-Serial"], "N/A") + args[2] = formatRequestValue(request["TLS-Cert-Expiration"], "N/A") + args[3] = formatRequestValue(request["TLS-Cert-Valid-Since"], "N/A") + args[4] = formatRequestValue(request["TLS-Cert-Subject"], "N/A") + args[5] = formatRequestValue(request["TLS-Cert-Issuer"], "N/A") + args[6] = formatRequestValue(request["TLS-Cert-Common-Name"], "N/A") + args[7] = formatRequestValue(request["TLS-Cert-Subject-Alt-Name-Email"], "N/A") + args[8] = formatRequestValue(request["TLS-Client-Cert-Serial"], "N/A") + args[9] = formatRequestValue(request["TLS-Client-Cert-Expiration"], "N/A") + args[10] = formatRequestValue(request["TLS-Client-Cert-Valid-Since"], "N/A") + args[11] = formatRequestValue(request["TLS-Client-Cert-Subject"], "N/A") + args[12] = formatRequestValue(request["TLS-Client-Cert-Issuer"], "N/A") + args[13] = formatRequestValue(request["TLS-Client-Cert-Common-Name"], "N/A") + args[14] = formatRequestValue(request["TLS-Client-Cert-Subject-Alt-Name-Email"], "N/A") + args[15] = formatRequestValue(request["TLS-Client-Cert-X509v3-Extended-Key-Usage"], "N/A") + args[16] = formatRequestValue(request["TLS-Client-Cert-X509v3-Subject-Key-Identifier"], "N/A") + args[17] = formatRequestValue(request["TLS-Client-Cert-X509v3-Authority-Key-Identifier"], "N/A") + args[18] = formatRequestValue(request["TLS-Client-Cert-X509v3-Extended-Key-Usage-OID"], "N/A") + return args +} + func formatRequest(request map[string]interface{}) string { parts := []string{} keys := util.MapKeys(request) @@ -312,7 +407,6 @@ func escapeRadiusRequest(s string) string { if size == len(s) { return s } - out := make([]byte, size) j := 0 for _, c := range []byte(s) { @@ -370,114 +464,114 @@ func parseRequestArgs(request map[string]interface{}) map[string]interface{} { type AKMSuite int const ( - AKMReserved AKMSuite = iota // 0 - Reserved - IEEE8021X // 1 - 802.1X - PSK // 2 - PSK - FT_8021X // 3 - FT over 802.1X - FT_PSK // 4 - FT over PSK - WPA_8021X // 5 - WPA with 802.1X - WPA_PSK // 6 - WPA with PSK - OWE // 7 - OWE - OWE_Transition // 8 - OWE Transition Mode - SAE // 9 - Simultaneous Authentication of Equals - FT_SAE // 10 - FT over SAE - FILS_SHA256 // 11 - FILS-SHA256 - FILS_SHA384 // 12 - FILS-SHA384 - FT_FILS_SHA256 // 13 - FT over FILS-SHA256 - FT_FILS_SHA384 // 14 - FT over FILS-SHA384 - OWE_transition_mode // 15 - OWE transition mode + AKMReserved AKMSuite = iota // 0 - Reserved + IEEE8021X // 1 - 802.1X + PSK // 2 - PSK + FT_8021X // 3 - FT over 802.1X + FT_PSK // 4 - FT over PSK + WPA_8021X // 5 - WPA with 802.1X + WPA_PSK // 6 - WPA with PSK + OWE // 7 - OWE + OWE_Transition // 8 - OWE Transition Mode + SAE // 9 - Simultaneous Authentication of Equals + FT_SAE // 10 - FT over SAE + FILS_SHA256 // 11 - FILS-SHA256 + FILS_SHA384 // 12 - FILS-SHA384 + FT_FILS_SHA256 // 13 - FT over FILS-SHA256 + FT_FILS_SHA384 // 14 - FT over FILS-SHA384 + OWE_transition_mode // 15 - OWE transition mode ) type CipherSuite int const ( - CipherReserved CipherSuite = iota // 0 - Reserved - WEP40 // 1 - WEP-40 - TKIP // 2 - TKIP - CipherReserved3 // 3 - Reserved - CCMP128 // 4 - CCMP-128 - WEP104 // 5 - WEP-104 - BIPCMAC128 // 6 - BIP-CMAC-128 - GCMP128 // 7 - GCMP-128 - GCMP256 // 8 - GCMP-256 - CCMP256 // 9 - CCMP-256 - BIPGMAC128 // 10 - BIP-GMAC-128 - BIPGMAC256 // 11 - BIP-GMAC-256 - SMS4 // 12 - SMS4 - CKIP128 // 13 - CKIP-128 - CKIP128_PMK // 14 - CKIP-128 with PMK caching - CipherReserved15 // 15 - Reserved + CipherReserved CipherSuite = iota // 0 - Reserved + WEP40 // 1 - WEP-40 + TKIP // 2 - TKIP + CipherReserved3 // 3 - Reserved + CCMP128 // 4 - CCMP-128 + WEP104 // 5 - WEP-104 + BIPCMAC128 // 6 - BIP-CMAC-128 + GCMP128 // 7 - GCMP-128 + GCMP256 // 8 - GCMP-256 + CCMP256 // 9 - CCMP-256 + BIPGMAC128 // 10 - BIP-GMAC-128 + BIPGMAC256 // 11 - BIP-GMAC-256 + SMS4 // 12 - SMS4 + CKIP128 // 13 - CKIP-128 + CKIP128_PMK // 14 - CKIP-128 with PMK caching + CipherReserved15 // 15 - Reserved ) -func(c CipherSuite) String() string { +func (c CipherSuite) String() string { switch c { - case WEP40: - return "WEP-40" - case TKIP: - return "TKIP" - case CCMP128: - return "CCMP-128" - case WEP104: - return "WEP-104" - case GCMP128: - return "GCMP-128" - case GCMP256: - return "GCMP-256" - case CCMP256: - return "CCMP-256" - case BIPCMAC128: - return "BIP-CMAC-128" - case BIPGMAC128: - return "BIP-GMAC-128" - case BIPGMAC256: - return "BIP-GMAC-256" - case SMS4: - return "SMS4" - case CKIP128: - return "CKIP-128" - case CKIP128_PMK: - return "CKIP-128 with PMK caching" - case CipherReserved3, CipherReserved15: - return "Reserved" - default: - return fmt.Sprintf("Unknown cipher suite (Value: %d)", c) + case WEP40: + return "WEP-40" + case TKIP: + return "TKIP" + case CCMP128: + return "CCMP-128" + case WEP104: + return "WEP-104" + case GCMP128: + return "GCMP-128" + case GCMP256: + return "GCMP-256" + case CCMP256: + return "CCMP-256" + case BIPCMAC128: + return "BIP-CMAC-128" + case BIPGMAC128: + return "BIP-GMAC-128" + case BIPGMAC256: + return "BIP-GMAC-256" + case SMS4: + return "SMS4" + case CKIP128: + return "CKIP-128" + case CKIP128_PMK: + return "CKIP-128 with PMK caching" + case CipherReserved3, CipherReserved15: + return "Reserved" + default: + return fmt.Sprintf("Unknown cipher suite (Value: %d)", c) } } -func(a AKMSuite) String() string { +func (a AKMSuite) String() string { switch a { - case IEEE8021X: - return "802.1X" - case PSK: - return "PSK" - case FT_8021X: - return "FT over 802.1X" - case FT_PSK: - return "FT over PSK" - case WPA_8021X: - return "WPA with 802.1X" - case WPA_PSK: - return "WPA with PSK" - case OWE: - return "OWE" - case OWE_Transition: - return "OWE Transition Mode" - case SAE: - return "SAE" - case FT_SAE: - return "FT over SAE" - case FILS_SHA256: - return "FILS-SHA256" - case FILS_SHA384: - return "FILS-SHA384" - case FT_FILS_SHA256: - return "FT over FILS-SHA256" - case FT_FILS_SHA384: - return "FT over FILS-SHA384" - case OWE_transition_mode: - return "OWE transition mode" - default: - return fmt.Sprintf("Unknown or Reserved AKM suite (Value: %d)", a) + case IEEE8021X: + return "802.1X" + case PSK: + return "PSK" + case FT_8021X: + return "FT over 802.1X" + case FT_PSK: + return "FT over PSK" + case WPA_8021X: + return "WPA with 802.1X" + case WPA_PSK: + return "WPA with PSK" + case OWE: + return "OWE" + case OWE_Transition: + return "OWE Transition Mode" + case SAE: + return "SAE" + case FT_SAE: + return "FT over SAE" + case FILS_SHA256: + return "FILS-SHA256" + case FILS_SHA384: + return "FILS-SHA384" + case FT_FILS_SHA256: + return "FT over FILS-SHA256" + case FT_FILS_SHA384: + return "FT over FILS-SHA384" + case OWE_transition_mode: + return "OWE transition mode" + default: + return fmt.Sprintf("Unknown or Reserved AKM suite (Value: %d)", a) } } diff --git a/go/plugin/caddy2/pfpki/models/models.go b/go/plugin/caddy2/pfpki/models/models.go index cfe0b350d842..c1048cb75995 100644 --- a/go/plugin/caddy2/pfpki/models/models.go +++ b/go/plugin/caddy2/pfpki/models/models.go @@ -1243,7 +1243,10 @@ func (c Cert) New() (types.Info, error) { Subject := c.MakeSubject() - NotAfter := time.Now().AddDate(0, 0, prof.Validity) + NotAfter := c.ValidUntil + if c.ValidUntil.IsZero() { + NotAfter = time.Now().AddDate(0, 0, prof.Validity) + } // Prepare certificate cert := &x509.Certificate{ @@ -1268,6 +1271,7 @@ func (c Cert) New() (types.Info, error) { if len(c.Mail) > 0 { Email = c.Mail } + if len(Email) > 0 { for _, mail := range strings.Split(Email, ",") { cert.EmailAddresses = append(cert.EmailAddresses, mail) diff --git a/html/captive-portal/lib/captiveportal/PacketFence/DynamicRouting/Module/TLSEnrollment.pm b/html/captive-portal/lib/captiveportal/PacketFence/DynamicRouting/Module/TLSEnrollment.pm index a0a605271e57..e006d6435015 100644 --- a/html/captive-portal/lib/captiveportal/PacketFence/DynamicRouting/Module/TLSEnrollment.pm +++ b/html/captive-portal/lib/captiveportal/PacketFence/DynamicRouting/Module/TLSEnrollment.pm @@ -195,7 +195,7 @@ sub get_bundle { my $mac = $self->current_mac; my $user_cache = $self->app->user_cache; my $pki_session = $user_cache->compute("pki_session", sub {}); - my $cert_content = $pki_provider->get_bundle({ certificate_email => $pki_session->{certificate_email}, certificate_cn => $pki_session->{certificate_cn}, certificate_pwd => $pki_session->{certificate_pwd} }); + my $cert_content = $pki_provider->get_bundle({ certificate_email => $pki_session->{certificate_email}, certificate_cn => $pki_session->{certificate_cn}, certificate_pwd => $pki_session->{certificate_pwd}, unregdate => $self->new_node_info->{'unregdate'}}); get_logger->debug(sub { "cert_content from pki service $cert_content" }); unless(defined($cert_content)){ diff --git a/html/pfappserver/root/src/views/Configuration/pkiProviders/_components/FormTypePacketfencePki.vue b/html/pfappserver/root/src/views/Configuration/pkiProviders/_components/FormTypePacketfencePki.vue index 7ad0062df9ec..c255e49eb65f 100644 --- a/html/pfappserver/root/src/views/Configuration/pkiProviders/_components/FormTypePacketfencePki.vue +++ b/html/pfappserver/root/src/views/Configuration/pkiProviders/_components/FormTypePacketfencePki.vue @@ -62,6 +62,13 @@ disabled-value="N" /> + + (is => 'rw', default => '%s'); has revoke_on_unregistration => (is => 'rw', default => 'N'); +has certificate_validity_time_from_unreg_date => (is => 'rw', default => 'N'); + =head2 country What country to use for the certificate diff --git a/lib/pf/pki_provider/packetfence_pki.pm b/lib/pf/pki_provider/packetfence_pki.pm index 211cfb444a9a..1839dd304f9f 100644 --- a/lib/pf/pki_provider/packetfence_pki.pm +++ b/lib/pf/pki_provider/packetfence_pki.pm @@ -20,6 +20,9 @@ use URI::Escape::XS qw(uri_escape uri_unescape); use pf::api::unifiedapiclient; use pf::dal::key_value_storage; use pf::error qw(is_success is_error); +use DateTime::TimeZone; +use DateTime::Format::Strptime; +use pf::util qw(isenabled); extends 'pf::pki_provider'; @@ -55,11 +58,8 @@ sub get_bundle { my $street = $self->streetaddress; my $postalcode = $self->postalcode; my $streetaddress = $self->streetaddress; - - my $certpwd = $args->{'certificate_pwd'}; - - my $value = eval { - my $return = pf::api::unifiedapiclient->default_client->call("POST", "/api/v1/pki/certs", { + my $expiration = $args->{'unregdate'}; + my $payload = { "cn" => $cn, "mail" => $email, "organisation" => $organisation, @@ -69,7 +69,22 @@ sub get_bundle { "postal_code" => $postalcode, "street_address" => $streetaddress, "profile_id" => $profile, - }); + }; + + if (defined($expiration) && $expiration ne "" && isenabled($self->certificate_validity_time_from_unreg_date) ) { + my $tz = $ENV{TZ} || DateTime::TimeZone->new( name => 'local' )->name(); + my $formatter = DateTime::Format::Strptime->new(pattern => "%F %T",time_zone=>$tz); + my $dt_obj = $formatter->parse_datetime($expiration); + # to convert to a different zone: + $dt_obj->set_time_zone('UTC'); + my $dt = $dt_obj->strftime("%Y-%m-%dT%T%z"); + $dt =~ s/(.*)\+(\d{2})(\d{2})/$1Z/g; + $payload->{"valid_until"} = "$dt"; + } + my $certpwd = $args->{'certificate_pwd'}; + + my $value = eval { + my $return = pf::api::unifiedapiclient->default_client->call("POST", "/api/v1/pki/certs", $payload); }; if ($@) { $logger->warn("Certificate creation failed");