2626import org .apache .cloudstack .framework .kms .KMSProvider ;
2727import org .apache .cloudstack .framework .kms .KeyPurpose ;
2828import org .apache .cloudstack .framework .kms .WrappedKey ;
29+ import org .apache .cloudstack .kms .HSMProfileVO ;
30+ import org .apache .cloudstack .kms .dao .HSMProfileDao ;
2931import org .apache .cloudstack .kms .provider .database .KMSDatabaseKekObjectVO ;
3032import org .apache .cloudstack .kms .provider .database .dao .KMSDatabaseKekObjectDao ;
3133import org .apache .commons .lang3 .StringUtils ;
34+
35+ import com .cloud .utils .db .SearchBuilder ;
36+ import com .cloud .utils .db .SearchCriteria ;
3237import org .apache .logging .log4j .LogManager ;
3338import org .apache .logging .log4j .Logger ;
3439
3843import java .util .Arrays ;
3944import java .util .Base64 ;
4045import java .util .Date ;
46+ import java .util .List ;
4147
4248/**
4349 * Database-backed KMS provider that stores master KEKs in a PKCS#11-like object table.
@@ -56,9 +62,22 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
5662 private static final String CKO_SECRET_KEY = "CKO_SECRET_KEY" ;
5763 private static final String CKK_AES = "CKK_AES" ;
5864
65+ private static final String DEFAULT_PROFILE_NAME = "default" ;
66+ private static final long SYSTEM_ACCOUNT_ID = 1L ;
67+ private static final long ROOT_DOMAIN_ID = 1L ;
68+
5969 private final SecureRandom secureRandom = new SecureRandom ();
6070 @ Inject
6171 private KMSDatabaseKekObjectDao kekObjectDao ;
72+ @ Inject
73+ private HSMProfileDao hsmProfileDao ;
74+
75+ @ Override
76+ public boolean start () {
77+ super .start ();
78+ ensureDefaultHSMProfile ();
79+ return true ;
80+ }
6281
6382 @ Override
6483 public String getProviderName () {
@@ -321,6 +340,43 @@ private void updateLastUsed(String kekLabel) {
321340 }
322341 }
323342
343+ /**
344+ * Seeds the default database HSM profile if it does not already exist.
345+ * This runs at provider startup to avoid FK constraint issues that occur
346+ * when the INSERT is placed in the schema upgrade SQL script (the account
347+ * table may not yet be populated when the upgrade script executes on a
348+ * fresh install).
349+ */
350+ private void ensureDefaultHSMProfile () {
351+ try {
352+ SearchBuilder <HSMProfileVO > sb = hsmProfileDao .createSearchBuilder ();
353+ sb .and ("name" , sb .entity ().getName (), SearchCriteria .Op .EQ );
354+ sb .and ("system" , sb .entity ().isSystem (), SearchCriteria .Op .EQ );
355+ sb .and ("protocol" , sb .entity ().getProtocol (), SearchCriteria .Op .EQ );
356+ sb .done ();
357+
358+ SearchCriteria <HSMProfileVO > sc = sb .create ();
359+ sc .setParameters ("name" , DEFAULT_PROFILE_NAME );
360+ sc .setParameters ("system" , true );
361+ sc .setParameters ("protocol" , PROVIDER_NAME );
362+
363+ List <HSMProfileVO > existing = hsmProfileDao .customSearchIncludingRemoved (sc , null );
364+ if (existing != null && !existing .isEmpty ()) {
365+ logger .debug ("Default database HSM profile already exists (id={})" , existing .get (0 ).getId ());
366+ return ;
367+ }
368+
369+ HSMProfileVO profile = new HSMProfileVO (DEFAULT_PROFILE_NAME , PROVIDER_NAME ,
370+ SYSTEM_ACCOUNT_ID , ROOT_DOMAIN_ID , null , null );
371+ profile .setEnabled (false );
372+ profile .setSystem (true );
373+ hsmProfileDao .persist (profile );
374+ logger .info ("Seeded default database HSM profile (id={}, uuid={})" , profile .getId (), profile .getUuid ());
375+ } catch (Exception e ) {
376+ logger .warn ("Failed to seed default database HSM profile: {}" , e .getMessage (), e );
377+ }
378+ }
379+
324380
325381 @ Override
326382 public String getConfigComponentName () {
0 commit comments