diff --git a/management/server/http/testing/integration/networks_handler_integration_test.go b/management/server/http/testing/integration/networks_handler_integration_test.go index 54f204a8f..a0a49a9ec 100644 --- a/management/server/http/testing/integration/networks_handler_integration_test.go +++ b/management/server/http/testing/integration/networks_handler_integration_test.go @@ -1319,7 +1319,7 @@ func Test_NetworkRouters_Update(t *testing.T) { }, }, { - name: "Update non-existing router creates it", + name: "Update non-existing router returns not found", networkId: "testNetworkId", routerId: "nonExistingRouterId", requestBody: &api.NetworkRouterRequest{ @@ -1328,11 +1328,7 @@ func Test_NetworkRouters_Update(t *testing.T) { Metric: 100, Enabled: true, }, - expectedStatus: http.StatusOK, - verifyResponse: func(t *testing.T, router *api.NetworkRouter) { - t.Helper() - assert.Equal(t, "nonExistingRouterId", router.Id) - }, + expectedStatus: http.StatusNotFound, }, { name: "Update router with both peer and peer_groups", diff --git a/management/server/networks/manager_test.go b/management/server/networks/manager_test.go index 6fb19d157..24d5f49b7 100644 --- a/management/server/networks/manager_test.go +++ b/management/server/networks/manager_test.go @@ -34,8 +34,11 @@ func Test_GetAllNetworksReturnsNetworks(t *testing.T) { networks, err := manager.GetAllNetworks(ctx, accountID, userID) require.NoError(t, err) - require.Len(t, networks, 1) - require.Equal(t, "testNetworkId", networks[0].ID) + ids := make([]string, 0, len(networks)) + for _, n := range networks { + ids = append(ids, n.ID) + } + require.ElementsMatch(t, []string{"testNetworkId", "secondNetworkId"}, ids) } func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) { diff --git a/management/server/networks/routers/manager.go b/management/server/networks/routers/manager.go index c7c3f2ff4..ed5b0e558 100644 --- a/management/server/networks/routers/manager.go +++ b/management/server/networks/routers/manager.go @@ -102,7 +102,7 @@ func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *t router.ID = xid.New().String() - err = transaction.SaveNetworkRouter(ctx, router) + err = transaction.CreateNetworkRouter(ctx, router) if err != nil { return fmt.Errorf("failed to create network router: %w", err) } @@ -162,11 +162,20 @@ func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *t return fmt.Errorf("failed to get network: %w", err) } - if network.ID != router.NetworkID { + existing, err := transaction.GetNetworkRouterByID(ctx, store.LockingStrengthUpdate, router.AccountID, router.ID) + if err != nil { + return fmt.Errorf("failed to get network router: %w", err) + } + + if existing.AccountID != router.AccountID { + return status.NewNetworkRouterNotFoundError(router.ID) + } + + if existing.NetworkID != router.NetworkID { return status.NewRouterNotPartOfNetworkError(router.ID, router.NetworkID) } - err = transaction.SaveNetworkRouter(ctx, router) + err = transaction.UpdateNetworkRouter(ctx, router) if err != nil { return fmt.Errorf("failed to update network router: %w", err) } diff --git a/management/server/networks/routers/manager_test.go b/management/server/networks/routers/manager_test.go index 6be90baa7..7b6d5f14f 100644 --- a/management/server/networks/routers/manager_test.go +++ b/management/server/networks/routers/manager_test.go @@ -195,6 +195,7 @@ func Test_UpdateRouterSuccessfully(t *testing.T) { if err != nil { require.NoError(t, err) } + router.ID = "testRouterId" s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) if err != nil { @@ -210,6 +211,102 @@ func Test_UpdateRouterSuccessfully(t *testing.T) { require.Equal(t, router.Metric, updatedRouter.Metric) } +func Test_UpdateRouterRejectsCrossAccountID(t *testing.T) { + ctx := context.Background() + userID := "testAdminId" + + // Admin of testAccountId tries to update a router that belongs to otherAccountId + // by passing the other account's router ID through the URL. + router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1, true) + if err != nil { + require.NoError(t, err) + } + router.ID = "otherRouterId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManager(s) + am := mock_server.MockAccountManager{} + manager := NewManager(s, permissionsManager, &am) + + updatedRouter, err := manager.UpdateRouter(ctx, userID, router) + require.Error(t, err) + require.Nil(t, updatedRouter) + + // The other account's router must be untouched. + stored, err := s.GetNetworkRouterByID(ctx, store.LockingStrengthNone, "otherAccountId", "otherRouterId") + require.NoError(t, err) + require.Equal(t, "otherAccountId", stored.AccountID) + require.Equal(t, "otherNetworkId", stored.NetworkID) + require.Equal(t, "otherPeer", stored.Peer) + require.Equal(t, 1, stored.Metric) +} + +func Test_CreateRouterRejectsCrossAccountID(t *testing.T) { + ctx := context.Background() + userID := "testAdminId" + + // Admin of testAccountId tries to create a router in otherAccountId's network. + // The permission check is on router.AccountID (their own), but the network + // lookup must fail because (testAccountId, otherNetworkId) does not exist. + router, err := types.NewNetworkRouter("testAccountId", "otherNetworkId", "testPeerId", []string{}, false, 1, true) + if err != nil { + require.NoError(t, err) + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManager(s) + am := mock_server.MockAccountManager{} + manager := NewManager(s, permissionsManager, &am) + + createdRouter, err := manager.CreateRouter(ctx, userID, router) + require.Error(t, err) + require.Nil(t, createdRouter) + + // No router should have been created in either account's scope under otherNetworkId. + routersInOther, err := s.GetNetworkRoutersByNetID(ctx, store.LockingStrengthNone, "otherAccountId", "otherNetworkId") + require.NoError(t, err) + require.Len(t, routersInOther, 1) + require.Equal(t, "otherRouterId", routersInOther[0].ID) +} + +func Test_UpdateRouterRejectsNetworkMismatch(t *testing.T) { + ctx := context.Background() + userID := "testAdminId" + + // The router exists in testNetworkId, but the caller submits secondNetworkId + // (a different network in the same account). The update must be refused. + router, err := types.NewNetworkRouter("testAccountId", "secondNetworkId", "testPeerId", []string{}, false, 1, true) + if err != nil { + require.NoError(t, err) + } + router.ID = "testRouterId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManager(s) + am := mock_server.MockAccountManager{} + manager := NewManager(s, permissionsManager, &am) + + updatedRouter, err := manager.UpdateRouter(ctx, userID, router) + require.Error(t, err) + require.Nil(t, updatedRouter) + + stored, err := s.GetNetworkRouterByID(ctx, store.LockingStrengthNone, "testAccountId", "testRouterId") + require.NoError(t, err) + require.Equal(t, "testNetworkId", stored.NetworkID) +} + func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) { ctx := context.Background() userID := "testUserId" diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index f3c6b741b..279c0e21f 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -4315,11 +4315,27 @@ func (s *SqlStore) GetNetworkRouterByID(ctx context.Context, lockStrength Lockin return netRouter, nil } -func (s *SqlStore) SaveNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error { - result := s.db.Save(router) +func (s *SqlStore) CreateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error { + if err := s.db.Create(router).Error; err != nil { + log.WithContext(ctx).Errorf("failed to create network router in store: %v", err) + return status.Errorf(status.Internal, "failed to create network router in store") + } + + return nil +} + +func (s *SqlStore) UpdateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error { + result := s.db. + Select("*"). + Where(accountAndIDQueryCondition, router.AccountID, router.ID). + Updates(router) if result.Error != nil { - log.WithContext(ctx).Errorf("failed to save network router to store: %v", result.Error) - return status.Errorf(status.Internal, "failed to save network router to store") + log.WithContext(ctx).Errorf("failed to update network router in store: %v", result.Error) + return status.Errorf(status.Internal, "failed to update network router in store") + } + + if result.RowsAffected == 0 { + return status.NewNetworkRouterNotFoundError(router.ID) } return nil diff --git a/management/server/store/sql_store_test.go b/management/server/store/sql_store_test.go index 7515add62..41e3290b6 100644 --- a/management/server/store/sql_store_test.go +++ b/management/server/store/sql_store_test.go @@ -2399,7 +2399,7 @@ func TestSqlStore_GetNetworkRouterByID(t *testing.T) { } } -func TestSqlStore_SaveNetworkRouter(t *testing.T) { +func TestSqlStore_CreateNetworkRouter(t *testing.T) { store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir()) t.Cleanup(cleanup) require.NoError(t, err) @@ -2410,7 +2410,7 @@ func TestSqlStore_SaveNetworkRouter(t *testing.T) { netRouter, err := routerTypes.NewNetworkRouter(accountID, networkID, "", []string{"net-router-grp"}, true, 0, true) require.NoError(t, err) - err = store.SaveNetworkRouter(context.Background(), netRouter) + err = store.CreateNetworkRouter(context.Background(), netRouter) require.NoError(t, err) savedNetRouter, err := store.GetNetworkRouterByID(context.Background(), LockingStrengthNone, accountID, netRouter.ID) @@ -2418,6 +2418,39 @@ func TestSqlStore_SaveNetworkRouter(t *testing.T) { require.Equal(t, netRouter, savedNetRouter) } +func TestSqlStore_UpdateNetworkRouter(t *testing.T) { + store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir()) + t.Cleanup(cleanup) + require.NoError(t, err) + + accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" + networkID := "ct286bi7qv930dsrrug0" + routerID := "ctc20ji7qv9ck2sebc80" + + netRouter := &routerTypes.NetworkRouter{ + ID: routerID, + AccountID: accountID, + NetworkID: networkID, + Peer: "", + PeerGroups: []string{"net-router-grp"}, + Masquerade: true, + Metric: 42, + Enabled: true, + } + + err = store.UpdateNetworkRouter(context.Background(), netRouter) + require.NoError(t, err) + + savedNetRouter, err := store.GetNetworkRouterByID(context.Background(), LockingStrengthNone, accountID, routerID) + require.NoError(t, err) + require.Equal(t, netRouter, savedNetRouter) + + // Updating a router under a different account must not match any row. + netRouter.AccountID = "non-existent-account" + err = store.UpdateNetworkRouter(context.Background(), netRouter) + require.Error(t, err) +} + func TestSqlStore_DeleteNetworkRouter(t *testing.T) { store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir()) t.Cleanup(cleanup) diff --git a/management/server/store/store.go b/management/server/store/store.go index 42cdcf36d..39b1c0ed3 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -228,7 +228,8 @@ type Store interface { GetNetworkRoutersByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*routerTypes.NetworkRouter, error) GetNetworkRoutersByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*routerTypes.NetworkRouter, error) GetNetworkRouterByID(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) (*routerTypes.NetworkRouter, error) - SaveNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error + CreateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error + UpdateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error DeleteNetworkRouter(ctx context.Context, accountID, routerID string) error GetNetworkResourcesByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*resourceTypes.NetworkResource, error) diff --git a/management/server/store/store_mock.go b/management/server/store/store_mock.go index 4f9d875d2..c7e86c2db 100644 --- a/management/server/store/store_mock.go +++ b/management/server/store/store_mock.go @@ -310,6 +310,20 @@ func (mr *MockStoreMockRecorder) CreateGroups(ctx, accountID, groups interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroups", reflect.TypeOf((*MockStore)(nil).CreateGroups), ctx, accountID, groups) } +// CreateNetworkRouter mocks base method. +func (m *MockStore) CreateNetworkRouter(ctx context.Context, router *types0.NetworkRouter) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNetworkRouter", ctx, router) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNetworkRouter indicates an expected call of CreateNetworkRouter. +func (mr *MockStoreMockRecorder) CreateNetworkRouter(ctx, router interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNetworkRouter", reflect.TypeOf((*MockStore)(nil).CreateNetworkRouter), ctx, router) +} + // CreatePeerJob mocks base method. func (m *MockStore) CreatePeerJob(ctx context.Context, job *types2.Job) error { m.ctrl.T.Helper() @@ -2612,6 +2626,36 @@ func (mr *MockStoreMockRecorder) MarkPATUsed(ctx, patID interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPATUsed", reflect.TypeOf((*MockStore)(nil).MarkPATUsed), ctx, patID) } +// MarkPeerConnectedIfNewerSession mocks base method. +func (m *MockStore) MarkPeerConnectedIfNewerSession(ctx context.Context, accountID, peerID string, newSessionStartedAt int64) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarkPeerConnectedIfNewerSession", ctx, accountID, peerID, newSessionStartedAt) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarkPeerConnectedIfNewerSession indicates an expected call of MarkPeerConnectedIfNewerSession. +func (mr *MockStoreMockRecorder) MarkPeerConnectedIfNewerSession(ctx, accountID, peerID, newSessionStartedAt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerConnectedIfNewerSession", reflect.TypeOf((*MockStore)(nil).MarkPeerConnectedIfNewerSession), ctx, accountID, peerID, newSessionStartedAt) +} + +// MarkPeerDisconnectedIfSameSession mocks base method. +func (m *MockStore) MarkPeerDisconnectedIfSameSession(ctx context.Context, accountID, peerID string, sessionStartedAt int64) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarkPeerDisconnectedIfSameSession", ctx, accountID, peerID, sessionStartedAt) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarkPeerDisconnectedIfSameSession indicates an expected call of MarkPeerDisconnectedIfSameSession. +func (mr *MockStoreMockRecorder) MarkPeerDisconnectedIfSameSession(ctx, accountID, peerID, sessionStartedAt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerDisconnectedIfSameSession", reflect.TypeOf((*MockStore)(nil).MarkPeerDisconnectedIfSameSession), ctx, accountID, peerID, sessionStartedAt) +} + // MarkPendingJobsAsFailed mocks base method. func (m *MockStore) MarkPendingJobsAsFailed(ctx context.Context, accountID, peerID, jobID, reason string) error { m.ctrl.T.Helper() @@ -2822,20 +2866,6 @@ func (mr *MockStoreMockRecorder) SaveNetworkResource(ctx, resource interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveNetworkResource", reflect.TypeOf((*MockStore)(nil).SaveNetworkResource), ctx, resource) } -// SaveNetworkRouter mocks base method. -func (m *MockStore) SaveNetworkRouter(ctx context.Context, router *types0.NetworkRouter) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SaveNetworkRouter", ctx, router) - ret0, _ := ret[0].(error) - return ret0 -} - -// SaveNetworkRouter indicates an expected call of SaveNetworkRouter. -func (mr *MockStoreMockRecorder) SaveNetworkRouter(ctx, router interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveNetworkRouter", reflect.TypeOf((*MockStore)(nil).SaveNetworkRouter), ctx, router) -} - // SavePAT mocks base method. func (m *MockStore) SavePAT(ctx context.Context, pat *types2.PersonalAccessToken) error { m.ctrl.T.Helper() @@ -2892,36 +2922,6 @@ func (mr *MockStoreMockRecorder) SavePeerStatus(ctx, accountID, peerID, status i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SavePeerStatus", reflect.TypeOf((*MockStore)(nil).SavePeerStatus), ctx, accountID, peerID, status) } -// MarkPeerConnectedIfNewerSession mocks base method. -func (m *MockStore) MarkPeerConnectedIfNewerSession(ctx context.Context, accountID, peerID string, newSessionStartedAt int64) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "MarkPeerConnectedIfNewerSession", ctx, accountID, peerID, newSessionStartedAt) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// MarkPeerConnectedIfNewerSession indicates an expected call of MarkPeerConnectedIfNewerSession. -func (mr *MockStoreMockRecorder) MarkPeerConnectedIfNewerSession(ctx, accountID, peerID, newSessionStartedAt interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerConnectedIfNewerSession", reflect.TypeOf((*MockStore)(nil).MarkPeerConnectedIfNewerSession), ctx, accountID, peerID, newSessionStartedAt) -} - -// MarkPeerDisconnectedIfSameSession mocks base method. -func (m *MockStore) MarkPeerDisconnectedIfSameSession(ctx context.Context, accountID, peerID string, sessionStartedAt int64) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "MarkPeerDisconnectedIfSameSession", ctx, accountID, peerID, sessionStartedAt) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// MarkPeerDisconnectedIfSameSession indicates an expected call of MarkPeerDisconnectedIfSameSession. -func (mr *MockStoreMockRecorder) MarkPeerDisconnectedIfSameSession(ctx, accountID, peerID, sessionStartedAt interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerDisconnectedIfSameSession", reflect.TypeOf((*MockStore)(nil).MarkPeerDisconnectedIfSameSession), ctx, accountID, peerID, sessionStartedAt) -} - // SavePolicy mocks base method. func (m *MockStore) SavePolicy(ctx context.Context, policy *types2.Policy) error { m.ctrl.T.Helper() @@ -3173,6 +3173,20 @@ func (mr *MockStoreMockRecorder) UpdateGroups(ctx, accountID, groups interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGroups", reflect.TypeOf((*MockStore)(nil).UpdateGroups), ctx, accountID, groups) } +// UpdateNetworkRouter mocks base method. +func (m *MockStore) UpdateNetworkRouter(ctx context.Context, router *types0.NetworkRouter) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNetworkRouter", ctx, router) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNetworkRouter indicates an expected call of UpdateNetworkRouter. +func (mr *MockStoreMockRecorder) UpdateNetworkRouter(ctx, router interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNetworkRouter", reflect.TypeOf((*MockStore)(nil).UpdateNetworkRouter), ctx, router) +} + // UpdateProxyHeartbeat mocks base method. func (m *MockStore) UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error { m.ctrl.T.Helper() diff --git a/management/server/testdata/networks.sql b/management/server/testdata/networks.sql index bcb202084..911b3bb27 100644 --- a/management/server/testdata/networks.sql +++ b/management/server/testdata/networks.sql @@ -9,9 +9,13 @@ INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM CREATE TABLE `networks` (`id` text,`account_id` text,`name` text,`description` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_networks` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); INSERT INTO networks VALUES('testNetworkId','testAccountId','some-name','some-description'); +INSERT INTO networks VALUES('secondNetworkId','testAccountId','second-name','second-description'); CREATE TABLE `network_routers` (`id` text,`network_id` text,`account_id` text,`peer` text,`peer_groups` text,`masquerade` numeric,`metric` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_routers` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); INSERT INTO network_routers VALUES('testRouterId','testNetworkId','testAccountId','','["csquuo4jcko732k1ag00"]',0,9999); +INSERT INTO accounts VALUES('otherAccountId','','2024-10-02 16:01:38.000000000+00:00','other.com','private',1,'otherNetworkIdentifier','{"IP":"100.65.0.0","Mask":"//8AAA=="}','',0,'[]',0,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO networks VALUES('otherNetworkId','otherAccountId','other-net','other-description'); +INSERT INTO network_routers VALUES('otherRouterId','otherNetworkId','otherAccountId','otherPeer',NULL,0,1); CREATE TABLE `network_resources` (`id` text,`network_id` text,`account_id` text,`name` text,`description` text,`type` text,`address` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_resources` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); INSERT INTO network_resources VALUES('testResourceId','testNetworkId','testAccountId','some-name','some-description','host','3.3.3.3/32');