Analyse CIS Benchmark Report Empfehlungen zu GLOBAL_NAMES

Aktuell ist das Thema Datenbank Security sehr wichtig und dringend geworden. In den letzten 20 Jahren hat es dafür kaum Interesse gegeben. Viele Anbieter nutzen die CIS Benchmarks(tm) – beispielsweise für die Oracle Datenbank 19c – als Quelle für Ihre Checks und Empfehlungen.

Im CIS Benchmark steht unter anderem:
This CIS Benchmark was created using a consensus review process comprised of a global community of subject matter experts. The process combines real world experience with data-based information to create technology specific guidance to assist users to secure their environments. Consensus participants provide perspective from a diverse set of backgrounds including consulting, software development, audit and compliance, security research, operations, government, and legal.

Quelle: CIS – Center for Internet Security: Oracle Datenbank 19c Benchmark Version 1.1.0, Seite 7

Was nicht zu finden ist, ist der Hinweis, dass die Umsetzung der Empfehlungen ohne weiteres Hinterfragen dazu führen kann, dass die Applikation nicht mehr funktioniert. Schauen wir uns einmal die Empfehlung zum Instance Parameter global_names an. Die folgenden Zeilen sind vom CIS Benchmark übernommen:

Description:

The global_names setting requires that the name of a database link matches that of the remote database it will connect to. This setting should have a value of TRUE.

Rationale:

Not requiring database connections to match the domain that is being called remotely could allow unauthorized domain sources to potentially connect via brute-force tactics.

Quelle: CIS – Center for Internet Security: Oracle Datenbank 19c Benchmark Version 1.1.0, Kapitel 2.2.3 / Seite 27

Diese simple Änderung eines Instanz-Parameters kann Applikationen, die Datenbank-Links nutzen, funktionsunfähig machen. Abgesehen davon, ist die Begründung mehr als nur schwach. Warum soll sich ein Angreifer die Mühe machen, eine Datenbank und DB Links anzulegen, nur um brute-force Attacken auf Benutzer und Passwörter durchzuführen? Das geht doch viel einfacher und mit weniger Aufwand mit jedem beliebigen Oracle Client!

Welche Auswirkung hat diese Änderung?

Damit die Auswirkungen besser verständlich sind, muss man die Bestandteile des GLOBAL_NAMES kennen und verstehen.

Die Verwirrung wird noch größer, da es einerseits den Instance Parameter global_names und andererseits den GLOBAL_NAME der Datenbank gibt. Der Instance Parameter global_names kann nur die Werte TRUE oder FALSE enthalten.

Der GLOBAL_NAME setzt sich aus dem Namen der Datenbank (db_name) und der Datenbank Domain (db_domain) zusammen. Der DBCA schlägt die DNS-Domain als db_domain vor, dass ist aber nicht verpflichtend und nur eine der Möglichkeiten. In der Praxis wird die db_domain auch gerne genutzt um die verschiedenen Umgebungen (PROD, DEV, TEST, QA,…) zu unterscheiden, sofern man dies nicht mittels db_name umsetzt.

Was der CIS Benchmark und alle uns bekannten Security Scanner verschweigen: wird GLOBAL_NAMES auf TRUE gesetzt, müssen folgende Punkte beachtet werden:

  • Mittels „ALTER DATABASE RENAME GLOBAL_NAME TO '<db_name>.<db_domain>';“ muss der Globale Name der Datenbank korrigiert werden. Erfolgt dieser Schritt nicht, ist kein Connect via Datenbank-Link auf diese Datenbank möglich.
  • Es müssen alle Datenbanken, die über DB Links miteinander verknüpft sind, gleichzeitig umgestellt werden. Ein Connect zwischen Datenbanken mit unterschiedlichen Einstellungen ist nicht möglich.
  • Bei allen Datenbank-Links wird automatisch die DB_DOMAIN angehängt. Da diese mit Sicherheit nicht entsprechend bedacht wurde, würde voraussichtlich kein einziger DB Link mehr funktionieren.
  • Bei der Nutzung eines DB Links wird überprüft, ob der DB Link Name mit den GLOBAL_NAME der Zieldatenbank übereinstimmt. Dies wird in der Praxis meist nicht der Fall sein!
  • Wegen dieser Anforderungen darf es pro Ziel-Datenbank nur einen DB Link geben.
  • Somit müssen voraussichtlich alle DB Links gelöscht und mit geänderten Namen angelegt werden. Da die Namen der DB Links auch in vielen Datenbankobjekten (Views, Synonyme, PL/SQL und Java Code) vorkommen können, müssen diese dort meist ebenfalls geändert werden.

Somit hat die Änderung von GLOBAL_NAMES eine massive Auswirkung auf den Betrieb von Applikationen, die Datenbank-Links benötigen. Das kann von einer einfachen Betriebsstörung bis zum Stillstand der Anwendung reichen.

Das Fatale an genau dieser Empfehlung ist, dass sie keinerlei Rücksicht darauf nimmt, ob Datenbank-Links genutzt werden oder nicht. Es wird lediglich auf den aktuellen Wert von global_names abgefragt. Nutzt man keine Datenbank-Links, führt die Änderung des Parameters selbstverständlich zu keinen weiteren Problemen, da ja nur bestehende DB-Links betroffen sind. Die Änderung bewirkt also keinerlei Verbesserung der aktuellen Datenbank Security und ist somit sinnlos.

In einer realen Datenbank Umgebung muss man jedoch noch viele weitere Punkte beachten, bevor man den Parameter ändern darf:

  • Nutzt man Datenbank Links?
  • Welche anderen Datenbanken werden mit diesen Links angesprochen? Haben diese ihrerseits weiterführende Links (es müssen ja alle Datenbanken gleichzeitig umgestellt werden)?
  • In welchen Datenbank Objekten werden die DB-Links referenziert (Views, Synonyme, PL/SQL und Java Code)?
  • In welchen SQL Statements werden die DB-Links genutzt?

Beschreibung des Beispiel-Environments

In diesem Beispiel nutzen wir zwei Datenbanken. Eine non-CDB Datenbank mit dem Namen DB19 sowie eine Multitenant Datenbank db19ee mit einer PDB names PDB1.

  • In der Datenbank DB19 gibt es einen Benutzer DEMO sowie einige DB Links.
  • In der Pluggable Database PDB1 heisst der Benutzer DEMOPDB1 und hat ebenfalls einige DB Links.

In den Schemata DEMO sowie DEMOPDB1 simulieren wir mittels einfacher Views, dass die Datenbank-Links auch in Datenbankobjekten genutzt werden. Neben Views gibt es auch noch andere Datenbankobjekte, die relevant sein können:

  • SYNONYME
  • (stored) Code, egal ob PL/SQL, JAVA oder in Zukunft auch Java Script, etc.
  • MVIEWS
  • VIEWS

Setup für das Beispiel

In beiden Datenbanken ist der Instance Parameter global_names auf FALSE gesetzt.

TNSNAMES.ORA

Für unser Beispiel nutzen wir folgenden Einträge im TNSNAMES.ORA:

SHOP = 
 (DESCRIPTION=
  (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
  (CONNECT_DATA=(SERVICE_NAME=db19.world))
 )
FINANZ = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.world))
  )
DEMOPDB1 = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=pdb1))
  )

Erzeugen der Benutzer, DB Links und Views

Mit den folgenden SQL Scripts werden die Benutzer, DB-Links und Views angelegt.

sqlplus system/oracle@localhost:1521/pdb1

create user demopdb1 identified by demopdb1;
grant dba to demopdb1;

drop public database link FINANZ;
drop public database link SHOP;
drop public database link LAGER;

create public database link FINANZ 
       connect to demo identified by demo using 'FINANZ';
create public database link SHOP 
       connect to demo identified by demo using 'SHOP';
create public database link LAGER 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

connect demopdb1/demopdb1@localhost:1521/pdb1

create database link SHOPINFO 
       connect to demo identified by demo using 'SHOP';

connect system/oracle@localhost:1521/db19.world

create user demo identified by demo;
grant dba to demo;

connect demo/demo@localhost:1521/db19.world

create database link PDBLAGER 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';
create public database link LAGER 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

create or replace view PDBLAGER_VIEWS as 
select * from user_views@PDBLAGER;
create or replace view LAGER_DB as 
select * from V$DATABASE@LAGER;

connect demopdb1/demopdb1@localhost:1521/pdb1

create or replace view FINANZ_USERS as 
select * from all_users@FINANZ;
create or replace view SHOP_TABLES as 
select * from all_tables@SHOP;
create or replace view SHOPINFO_VIEWS as 
select * from all_views@SHOPINFO;
create or replace view LAGER_VIEWS as 
select * from user_views@LAGER;

Environment prüfen

Mit den folgenden Statements – jeweils als der Applikationsbenutzer DEMO bzw. DEMOPDB1 abgesetzt – kann man die aktuelle Konfiguration auslesen:

Script „check_env.sql“

set pages 50 lines 160 tab off
col GLOBAL_NAME for a12
col NAME for a20
col VALUE for a20
col owner for a16
col db_link for a16
col username for a16
col host for a16
col valid for a5

SELECT * FROM global_name;

SELECT con_id, name, value FROM v$SYSTEM_PARAMETER
WHERE name in ('db_name','db_domain','global_names') ;

SELECT owner, db_link, username, host, valid FROM dba_db_links;

Output für die Datenbank DB19 (non-CDB):

@check_env

GLOBAL_NAME
------------
DB19.WORLD

    CON_ID NAME                 VALUE
---------- -------------------- -----
         0 db_domain            world
         0 global_names         FALSE
         0 db_name              db19

OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES

DB19 wurde offensichtlich mit db_domain=world angelegt. Das war seitens Oracle viele Jahre lang (beginnend mit Oracle 7 oder 8) der Default für diesen Parameter. Da es sich um eine non-CDB Datenbank handelt, ist die CON_ID nicht relevant.

Testen wir jetzt, ob die Datenbank-Links aktuell funktionieren:

connect demo/demo@localhost:1521/db19.world

select view_name from user_views;

VIEW_NAME
---------------
PDBLAGER_VIEWS
LAGER_DB

select count(*) from PDBLAGER_VIEWS;

  COUNT(*)
----------
         4
         
select name from LAGER_DB;

NAME
--------------------
DB19EE

Das klappt schon mal.

Output für DB19EE / PDB1:

@check_env

GLOBAL_NAME
------------
PDB1

    CON_ID NAME                 VALUE
---------- -------------------- ------
         0 db_domain
         0 global_names         FALSE
         0 db_name              db19ee
         
OWNER     DB_LINK   USERNAME         HOST      VALID
--------- --------- ---------------- --------- -----
PUBLIC    FINANZ    DEMO             FINANZ    YES
PUBLIC    SHOP      DEMO             SHOP      YES
PUBLIC    LAGER     DEMOPDB1         DEMOPDB1  YES
DEMOPDB1  SHOPINFO  DEMO             SHOP      YES

Da die Instance Parameter alle mit CON_ID=0 aufgeführt werden, bedeutet das, dass die Parameter in der CDB ROOT gesetzt sind (und nicht für einzelne PDBs).

Auch hier wollen wir die Funktion der Views und DB Links überprüfen:

connect demopdb1/demopdb1@localhost:1521/pdb1

select view_name from user_views;

VIEW_NAME
---------------
FINANZ_USERS
SHOP_TABLES
SHOPINFO_VIEWS
LAGER_VIEWS

select count(*) from FINANZ_USERS;

  COUNT(*)
----------
         4

select count(*) from SHOP_TABLES;

  COUNT(*)
----------
      1800
      
select count(*) from SHOPINFO_VIEWS;      

  COUNT(*)
----------
      7073

select count(*) from LAGER_VIEWS;

  COUNT(*)
----------
         4

Alle Views liefern korrekte Ergebnisse. Die Abfragen auf die Views werden wir gleich wieder benötigen.

Ändern von GLOBAL_NAMES für die Datenbank DB19 (non-CDB)

Was passiert, wenn der DBA der Datenbank DB19 der Empfehlung eines Security Scanner Toolas folgt und global_names auf TRUE setzt ? Da der Parameter nicht dynamisch ist, muss die Instance neu gestartet werden:

sqlplus / as sysdba
alter system set global_names = TRUE scope=spfile;
shutdown immediate;
startup;

Keine Fehlermeldung, alles schaut gut aus. Führen wir nochmals die Prüfung des Environments durch:

@check_env

GLOBAL_NAME
------------
DB19.WORLD

CON_ID NAME          VALUE
------ ------------- --------------------
     0 db_domain     world
     0 global_names  TRUE  *** wurde korrekt geändert
     0 db_name       db19

OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES

Alles sieht gut aus! Die Änderung hat funktioniert.

Applikation starten und testen

Die Applikation startet (als Benutzer Demo) und greift auf die Views mit Datenbank-Links zu:

connect demo/demo@localhost:1521/db19.world

select count(*) from PDBLAGER_VIEWS;

ERROR at line 1:
ORA-02085: database link PDBLAGER.WORLD connects to PDB1
         
select name from LAGER_DB;

ERROR at line 1:
ORA-02085: database link LAGER.WORLD connects to PDB1

ORA-02085: database link LAGER.WORLD connects to PDB1

Jeder Zugriff scheitert mit ORA-02085. Was sagt Oracle zu dem Fehler?

02085, 00000, "database link %s connects to %s"
// *Cause: a database link connected to a database with a different name.
//  The connection is rejected.
// *Action: create a database link with the same name as the database it
//  connects to, or set global_names=false.
//

OK, nachdem wir global_names absichtlich so gesetzt haben, bleibt uns also nichts anderes übrig als die Datenbank-Links zu korrigieren, oder?

Noch ein Versuch:

CREATE DATABASE LINK PDB1 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

Database link created.

select name from v$database@PDB1;

ERROR at line 1:
ORA-02085: database link PDB1.WORLD connects to PDB1

Den Datenbank-Link jetzt einfach PDB1 zu nennen, hilft anscheinend nicht! In der Fehlermeldung steht jetzt aber PDB1.WORLD! Schauen wir uns nochmals das Environment an:

@check_env

GLOBAL_NAME
------------
DB19.WORLD

    CON_ID NAME                 VALUE
---------- -------------------- -------
         0 db_domain            world
         0 global_names         TRUE
         0 db_name              db19

OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES
DEMO    PDB1.WORLD       DEMOPDB1  DEMOPDB1  YES

Wie oben erwähnt, ist das der alte Defaultwert der db_domain! Alle Datenbanken, die mit Datenbank-Links verbunden sind, müssen entweder die gleiche db_domain haben, oder man muss die db_domain beim Anlegen des DB Links mit angeben.

Einfluss von DB_DOMAIN

Angenommen die db_domain der Ziel-Datenbank ist SCHULUNG, dann lässt sich der Link folgendermaßen anlegen.

drop database link PDB1;
CREATE DATABASE LINK PDB1.SCHULUNG 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';
Database link created.

SELECT owner, db_link, username, host, valid FROM dba_db_links;

OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES
DEMO    PDB1.SCHULUNG    DEMOPDB1  DEMOPDB1  YES

Wenn auf der Zieldatenbank der db_domain Parameter nicht gesetzt ist, funktioniert das nicht. Dann steht man vor der Entscheidung, ob man bei allen Datenbanken eine db_domain einführen oder diese auf allen Datenbanken löschen sollte. Dabei gibt es natürlich noch weitere Faktoren zu berücksichtigen:

  • Die db_domain ist Teil des SERVICE_NAMES bei Connect Strings (siehe TNSNAMES.ORA beim Setup).
  • Die db_domain spielt auch bei Data Guard Konfigurationen eine Rolle.
  • Da die Datenbank-Links nach dem Umsetzen des global_names Parameters mit der Kombination db_name.db_domain der Ziel Datenbank übereinstimmen müssen, wird es problematisch, wenn man den gleichen db_name in verschiedenen Umgebungen (Produktion, Test, Dev, Schulung, QA,…) genutzt hat. Die db_names und noch schlimmer: auch die Namen der PDBs müssen jetzt im Unternehmen eindeutig sein.
    Hand aufs Herz: Wer hat mit der Einführung von Multitenant Datenbanken darauf geachtet, dass die PDBs über alle CDBs hinweg eindeutige Namen haben?

Nach dem Abwägen aller dieser Punkte erscheint es am sinnvollsten, für die verschiedenen Umgebungen (PROD, DEV, TEST,…) entsprechende db_domains zu nutzen. Für unser Beispiel nehmen wir an, dass es sich bei beiden Datenbanken um Produktionsdatenbanken handelt und die db_domain somit auf PROD geändert werden soll. Da auch die db_domain nicht dynamisch geändert werden kann, gehen wir wie folgt vor:

alter system set db_domain=prod scope=spfile;

shutdown immediate;
startup;

Schauen wir uns wieder das Environment an:

@check_env

GLOBAL_NAME
------------
DB19.WORLD

    CON_ID NAME                 VALUE
---------- -------------------- ------
         0 db_domain            PROD
         0 global_names         TRUE
         0 db_name              db19
         
OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES
DEMO    PDB1.SCHULUNG    DEMOPDB1  DEMOPDB1  YES

Die db_domain ist jetzt PROD. Perfekt! Der GLOBAL_NAME der Datenbank steht aber noch auf DB19.WORLD! Das muss noch korrigiert werden:

alter database rename global_name to DB19.PROD;

GLOBAL_NAME
------------
DB19.PROD

Damit war die Umstellung doch ganz einfach und schmerzlos, oder?

ORA-12154: TNS:could not resolve the connect identifier specified

Wir starten die Applikation, die sich an der Datenbank anmelden will

sqlplus demo/demo@SHOP

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Aug 30 21:32:25 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12154: TNS:could not resolve the connect identifier specified

Ooops. Das Ändern der db_domain sorgt dafür, dass die Applikation sich nicht mehr anmelden kann! Der Grund liegt darin, dass eben die db_domain Teil des SERVICE_NAMENs ist und dieser jetzt DB19.PROD lautet. Der Listener zeigt das ganz deutlich:

lsnrctl services

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 30-AUG-2023 21:33:40

Copyright (c) 1991, 2023, Oracle.  All rights reserved.

Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))
Services Summary...
Service "db19.PROD" has 1 instance(s).
  Instance "db19", status READY, has 1 handler(s) for this service...
    Handler(s):
      "DEDICATED" established:0 refused:0 state:ready
         LOCAL SERVER
Service "db19XDB.PROD" has 1 instance(s).
  Instance "db19", status READY, has 1 handler(s) for this service...
    Handler(s):
      "D000" established:0 refused:0 current:0 max:1022 state:ready
         DISPATCHER <machine: ol8, pid: 80944>
         (ADDRESS=(PROTOCOL=tcp)(HOST=ol8.localdomain)(PORT=9873))

Somit müssen wir die Einträge aller TNSNAMES.ORA Dateien und aller anderen Stellen, in denen Connect Informationen vorkommen, korrigieren.

In der TNSNAMES.ora machen wir aus:

SHOP = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.world)))
FINANZ = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.world)))
DEMOPDB1 = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=pdb1)))

jetzt:

SHOP = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.prod)))
FINANZ =
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.prod)))
DEMOPDB1 = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=pdb1)))

Der nächste Versuch, sich mit der Applikation anzumelden ist jetzt erfolgreich:

sqlplus demo/demo@SHOP

Funktionieren die Datenbank-Links jetzt vielleicht schon?

ORA-02019: connection description for remote database not found

Wir versuchen wieder auf die View(s) auf der PDB1 zuzugreifen.

select count(*) from PDBLAGER_VIEWS;
                     *
ERROR at line 1:
ORA-02019: connection description for remote database not found

Jetzt bekommen wir einen neuen Fehler! Wie schaut der View aus, den wir da nutzen und welcher Datenbank-Link ist hier gemeint?

create or replace view PDBLAGER_VIEWS as select * from user_views@PDBLAGER;

OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES
DEMO    PDB1.SCHULUNG    DEMOPDB1  DEMOPDB1  YES

Was kann hier wieder das Problem sein? Erraten, die geänderte db_domain bereitet uns weiteres Ungemach. Der DB_LINK heisst noch „PDBLAGER.world“, da im SELECT aber keine Domain angegeben wurde, wird automatisch auf „PDBLAGER.prod“ ergänzt – nur: den DB Link gibt es nicht! Also legen wir diesen doch einfach an und probieren es aus:

create database link PDBLAGER.prod 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

select count(*) from PDBLAGER_VIEWS;
                     *
ERROR at line 1:
ORA-02085: database link PDBLAGER.PROD connects to PDB1

Anscheinend wird der Database Link jetzt zwar gefunden, wir scheitern aber wieder daran, dass der Datenbank-Link PDBLAGER auf PDB1 zeigt. Wir müssen natürlich einen DB Link PDB1.PROD erzeugen, weil wir ja alle Datenbanken auf die db_domain=PROD umstellen wollen:

create database link PDB1.prod 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

select name from v$database@pdb1;
                            *
ERROR at line 1:
ORA-02085: database link PDB1.PROD connects to PDB1

Solange wir die CDB Datenbank noch nicht umgestellt haben, werden wir leider nicht mehr via Database Link darauf zugreifen können. Also stellen wir auch die CDB Datenbank um.

Umstellen der CDB Datenbank mit der Pluggable Database PDB1

Was müssen wir alles beachten:

  • global_names auf TRUE
  • db_domain auf PROD
  • Datenbank GLOBAL_NAME korrigieren
  • Anpassungen in der/den TNSNAMES.ORA(s) bzw. Applikations Connectstrings
sqlplus / as sysdba
select * from global_name;

GLOBAL_NAME
------------
DB19EE

alter system set global_names=TRUE scope=spfile;
alter system set db_domain=PROD scope=spfile;

shutdown immediate;
startup;

alter database rename global_name to DB19EE.PROD;

select * from global_name;

GLOBAL_NAME
------------
DB19EE.PROD

Für die CDB passt der Globale Name jetzt.

Zur Sicherheit das Environment für die PDB1 verifizieren:

connect demopdb1/demopdb1@localhost/pdb1.prod;

GLOBAL_NAME
------------
PDB1

    CON_ID NAME                 VALUE
---------- -------------------- ------
         0 db_domain            PROD
         0 global_names         TRUE
         0 db_name              db19ee
         
OWNER     DB_LINK   USERNAME  HOST      VALID
--------- --------- --------- --------- -----
PUBLIC    FINANZ    DEMO      FINANZ    YES
PUBLIC    SHOP      DEMO      SHOP      YES
PUBLIC    LAGER     DEMOPDB1  DEMOPDB1  YES
DEMOPDB1  SHOPINFO  DEMO      SHOP      YES

GLOBAL_NAME in PDB1 anpassen

Auch in der PDB müssen wir den GLOBAL_NAME noch korrigieren, das ist leider etwas aufwändiger:

connect / as sysdba
select * from global_name;

GLOBAL_NAME
------------
DB19EE.PROD

alter pluggable database PDB1 close;
alter pluggable database PDB1 open restricted;
alter pluggable database PDB1 close;
alter pluggable database PDB1 open;

alter session set container = PDB1;

select * from global_name;

GLOBAL_NAME
------------
PDB1.PROD

Durch das Öffnen der PDB im restricted mode, wird die Änderung des GLOBAL_NAMES auf die gleiche Domain wie der CDB durchgeführt. Sollte dies wider Erwarten erwarten nicht automatisch funktionieren, muss der entsprechende
ALTER DATABASE RENAME GLOBAL_NAME ...;
in der mit restricted mode geöffneten PDB durchgeführt werden.

Zuletzt das TNSNAMES.ORA File wie folgt korrigieren.

SHOP = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.prod)))
FINANZ = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=db19.prod)))
DEMOPDB1 = 
  (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=pdb1.prod)))

Erfolgreicher Zugriff von der non-CDB Datenbank via DB Link auf PDB1

Kann man jetzt von der DB19 endlich auf die PDB1 mittels Datenbank-Link zugreifen?

connect demo/demo@localhost/db19.prod

select name from v$database@pdb1;

NAME
---------
DB19EE

Heureka, wir haben es geschafft. Der Zugriff von DB19 auf PDB1 via DB Link funktioniert! Klappt auch der Zugriff über die Views?

select view_name from user_views;

VIEW_NAME
---------------
PDBLAGER_VIEWS
LAGER_DB

select count(*) from PDBLAGER_VIEWS;
                     *
ERROR at line 1:
ORA-02085: database link PDBLAGER.PROD connects to PDB1.PROD

Natürlich nicht, weil in der View ja immer noch der „falsche“ Datenbank-Link enthalten ist.

Anpassungen von DB Links und Applikations Code auf der nonCDB Datenbank

Wir müssen somit mit den Datenbank-Links aufräumen und dann den Applikationscode ändern.

SELECT owner, db_link, username, host, valid FROM dba_db_links;

OWNER   DB_LINK          USERNAME  HOST      VALID
------- ---------------- --------- --------- -----
PUBLIC  LAGER.WORLD      DEMOPDB1  DEMOPDB1  YES
DEMO    PDBLAGER.WORLD   DEMOPDB1  DEMOPDB1  YES
DEMO    PDB1.PROD        DEMOPDB1  DEMOPDB1  YES
DEMO    PDB1.SCHULUNG    DEMOPDB1  DEMOPDB1  YES
DEMO    PDBLAGER.PROD    DEMOPDB1  DEMOPDB1  YES

drop public database link LAGER.WORLD;

drop database link PDBLAGER.WORLD;

drop database link PDB1.SCHULUNG;

drop database link PDBLAGER.PROD;

-- jetzt noch die View Codes reparieren:

create or replace view PDBLAGER_VIEWS as select * from user_views@PDB1;

create or replace view LAGER_DB as select * from V$DATABASE@PDB1;

select count(*) from PDBLAGER_VIEWS;

  COUNT(*)
----------
         4
         
select name from LAGER_DB;

NAME
--------------------
DB19EE

Anpassungen von DB Links und Applikations Code in der PDB1

Jetzt muss man das noch in der PDB1 Datenbank nachziehen.

connect demopdb1/demopdb1@localhost/pdb1.prod

SELECT owner, db_link, username, host, valid FROM dba_db_links;

OWNER     DB_LINK   USERNAME  HOST      VALID
--------- --------- --------- --------- -----
PUBLIC    FINANZ    DEMO      FINANZ    YES
PUBLIC    SHOP      DEMO      SHOP      YES
PUBLIC    LAGER     DEMOPDB1  DEMOPDB1  YES
DEMOPDB1  SHOPINFO  DEMO      SHOP      YES

-- alle vorhandenen DB Links einmal verwerfen
drop public database link FINANZ;
                          *
ERROR at line 1:
ORA-02024: database link not found

ORA-02024: database link not found

Jetzt haben wir wieder ein neues Problem. Sobald db_domain einen Wert hat, kann man Datenbank-Links OHNE Domain nicht mehr droppen! Das geht nur, wenn die db_domain wieder zurückgesetzt und alles neu gestartet wird. Dann könnte man die DB Links droppen.

Merke:
Die DB Links ohne db_domain muss man droppen bevor man die db_domain konfiguriert!

Also legen wir die jetzt benötigen DB Links zusätzlich an:

-- dieser ersetzt die DB Links FINANZ und SHOP:
create public database link db19 
       connect to demo identified by demo using 'SHOP';

select name from v$database@db19;

NAME
--------------------
DB19

-- jetzt brauchen wir noch den Ersatz für den DB Link Lager:
create public database link pdb1 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

ORA-02082: a loopback database link must have a connection qualifier

Ooops, das nächste Problem!

ORA-02082: a loopback database link must have a connection qualifier

Sobald man bei global_name=true einen Datenbank-Link auf die eigene Datenbank benötigt, geht das plötzlich nicht mehr so einfach. Was sagt die Fehlermeldung genau aus:

02082, 00000, "a loopback database link must have a connection qualifier"
// *Cause: An attempt was made to create a database link with the same name
//  as the current database.
// *Action: a loopback database link needs a trailing qualifier, for example
//  MYDB.EXAMPLE.COM@INST1  - the '@INST1' is the qualifier
//

Also fügen wir einen Qualifier hinzu

create public database link pdb1@loop1 
       connect to demopdb1 identified by demopdb1 using 'DEMOPDB1';

Database link created.

select name from v$database@pdb1@loop1;

NAME
--------------------
DB19EE

Funktioniert offensichtlich, aber es ist verwirrend, dass man hier zweimal „@“ hat! Ein weiterer Datenbank-Link fehlt uns noch, der Ersatz für SHOPINFO:

create database link db19 
       connect to demo identified by demo using 'SHOP';

select name from v$database@db19;

NAME
--------------------
DB19

Scheint zu passen. Prüfen wir jetzt die angelegten Datenbank-Links, bevor wir den View Code anpassen:

SELECT owner, db_link, username, host, valid 
  FROM dba_db_links;

OWNER     DB_LINK          USERNAME  HOST      VALID
--------- ---------------- --------- --------- -----
PUBLIC    FINANZ           DEMO      FINANZ    YES
PUBLIC    SHOP             DEMO      SHOP      YES
PUBLIC    DB19.PROD        DEMO      SHOP      YES
PUBLIC    LAGER            DEMOPDB1  DEMOPDB1  YES
DEMOPDB1  SHOPINFO         DEMO      SHOP      YES
DEMOPDB1  DB19.PROD        DEMO      SHOP      YES
PUBLIC    PDB1.PROD@LOOP1  DEMOPDB1  DEMOPDB1  YES

Etwas unübersichtlich, weil die „alten“ DB Links noch vorhanden sind. Die können wir aber ausblenden.

SELECT owner, db_link, username, host, valid 
  FROM dba_db_links where DB_LINK like '%.%';

OWNER     DB_LINK          USERNAME  HOST      VALID
--------- ---------------- --------- --------- -----
PUBLIC    DB19.PROD        DEMO      SHOP      YES
DEMOPDB1  DB19.PROD        DEMO      SHOP      YES
PUBLIC    PDB1.PROD@LOOP1  DEMOPDB1  DEMOPDB1  YES

Passt alles, oder haben wir ein neues Problem?

Datenbank-Links wieder eindeutig machen!

Wir haben zwei Datenbank-Links mit dem Namen DB19.PROD*. In diesem Beispiel haben wir das Glück, dass das Ziel der gleiche Benutzer ist. Was wäre, wenn die Links auf verschiedene Datenbankuser in DB19 zeigen sollen? Beispielsweise auf die Schemata SHOP_OWNER und FINANZ_OWNER? Außerdem ist auf den ersten Blick nicht klar, welcher der beiden DB Links genutzt wird. Bei Datenbank-Links verwendet die Datenbank immer den privaten Link, sofern es Namensgleichheiten gibt. Ist man z.B. mit dem Benutzer DEMOPDB1 angemeldet, würde der private Link genutzt werden. Für alle anderen Datenbank Benutzer wird der PUBLIC Datenbank-Link verwendet. Was wäre, wenn die Links jetzt auf verschiedene Schemata verbinden würden? Das Ergebnis wäre davon abhängig, mit welchem Datenbank Benutzer man angemeldet ist! Das bedeutet, dass man dies bei der Applikationsentwicklung entsprechend berücksichtigen muss! Ein möglicher Workaround ist die Nutzung von Qualifiern – hier ein Beispiel:

create public database link DB19@SHOP 
       connect to shop_owner identified by shop_owner_pwd using 'SHOP';

create public database link DB19@FINANZ 
       connect to finanz_owner identified by finanz_owner_pwd using 'FINANZ';

-- diese ersetzen den public link DB19, deshalb löschen wir diesen gleich:
drop public database link DB19;

Um dies zu testen, müssen wir die beiden Benutzer noch anlegen:

connect demo/demo@SHOP;

create user shop_owner identified by shop_owner_pwd;
grant dba to shop_owner;

create user finanz_owner identified by finanz_owner_pwd;
grant dba to finanz_owner;

DB Links testen:

connect demopdb1/demopdb1@DEMOPDB1;

select username from v$session@DB19@shop 
 where sid=(select distinct sid from v$mystat@DB19@shop);

USERNAME
----------------
SHOP_OWNER

select username from v$session@DB19@finanz 
 where sid=(select distinct sid from v$mystat@DB19@finanz);

USERNAME
----------------
FINANZ_OWNER

OWNER     DB_LINK          USERNAME      HOST      VALID
--------- ---------------- ------------- --------- -----
DEMOPDB1  DB19.PROD        DEMO          SHOP      YES
PUBLIC    PDB1.PROD@LOOP1  DEMOPDB1      DEMOPDB1  YES
PUBLIC    DB19.PROD@SHOP   SHOP_OWNER    SHOP      YES
PUBLIC    DB19.PROD@FINANZ FINANZ_OWNER  FINANZ    YES

Applikations Code in PDB1 anpassen

Jetzt müssen wir nur noch die View Codes korrigieren:

create or replace view FINANZ_USERS as select * from all_users@DB19@FINANZ;
create or replace view SHOP_TABLES as select * from all_tables@DB19@SHOP;
create or replace view SHOPINFO_VIEWS as select * from all_views@DB19@SHOP;
create or replace view LAGER_VIEWS as select * from user_views@PDB1@LOOP1;

select count(*) from FINANZ_USERS;

  COUNT(*)
----------
        27

select count(*) from SHOP_TABLES;

  COUNT(*)
----------
      1800

select count(*) from SHOPINFO_VIEWS;

  COUNT(*)
----------
      7073

select count(*) from LAGER_VIEWS;

  COUNT(*)
----------
         4

Nachdem die Ergebnisse mit den ursprünglichen übereinstimmen, scheinen wir die Umstellung geschafft zu haben. Sind damit alle etwaigen Probleme schon abgedeckt?

Leider nein, folgende Themen wurden nicht behandelt:

  • Die Connect Strings sind aktuell verwirrend, weil am Datenbank-Link (HOST) nicht erkennbar ist, dass es sich um die gleiche Datenbank handelt. Es ist empfehlenswert, im Zuge der Anpassungen auch die Connect-Strings gleichlautend mit dem GLOBAL_NAME zu benennen.
  • Manche Softwarelösungen nutzen den GLOBAL_NAME, um Linzenkeys an eine Datenbank zu knüpfen. Durch die Änderung des GLOBAL_NAME sind diese Lizenzkeys nicht mehr gültig.
  • Applikationshersteller haben oft hardcoded Datenbank-Link Namen in ihren verschlüsselten Codes (wrapped PL/SQL oder Java,…). Eine Änderung bedeutet, dass der Softwareentwickler dies in seiner Applikation ändern muss. Dazu sind nicht alle Softwarehersteller bereit.

Mit Sicherheit finden sich noch weitere Fallen, die hier nicht berücksichtigt wurden.

Zusammenfassung / Management Summary

Wenn man, wie viele Security Scanner empfehlen, einfach den Instance-Parameter global_names auf TRUE setzt und Datenbank-Links verwendet, läuft man Gefahr, dass die über den Link angeschlossenen Datenbanken ebenfalls betroffen sind und Applikationen nicht mehr funktionieren. Eine solche Änderung bedeutet massive Aufwände für DBAs und Entwickler und muss als eigenes Projekt geplant und umgesetzt werden, um den Betrieb der Applikationen sicherstellen zu können.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

This site uses Akismet to reduce spam. Learn how your comment data is processed.